Caps-iOS/Caps/Camera/CameraManager.swift
2023-04-17 14:18:30 +02:00

205 lines
5.6 KiB
Swift

import Foundation
import AVFoundation
class CameraManager: ObservableObject {
enum Status {
case unconfigured
case configured
case unauthorized
case failed
}
static let shared = CameraManager()
@Published var error: CameraError?
let session = AVCaptureSession()
private let sessionQueue = DispatchQueue(label: "de.christophhagen.cam")
private let videoOutput = AVCaptureVideoDataOutput()
private let photoOutput = AVCapturePhotoOutput()
private var status = Status.unconfigured
private var camera: AVCaptureDevice?
private init() {
configure()
}
private func set(error: CameraError?) {
DispatchQueue.main.async {
self.error = error
}
}
private func checkPermissions() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .notDetermined:
sessionQueue.suspend()
AVCaptureDevice.requestAccess(for: .video) { authorized in
if !authorized {
self.status = .unauthorized
self.set(error: .deniedAuthorization)
}
self.sessionQueue.resume()
}
case .restricted:
status = .unauthorized
set(error: .restrictedAuthorization)
case .denied:
status = .unauthorized
set(error: .deniedAuthorization)
case .authorized:
break
@unknown default:
status = .unauthorized
set(error: .unknownAuthorization)
}
}
private func configureCaptureSession() {
guard status == .unconfigured else {
return
}
session.beginConfiguration()
session.sessionPreset = .photo
defer {
session.commitConfiguration()
}
self.camera = AVCaptureDevice.default(
.builtInWideAngleCamera,
for: .video,
position: .back)
guard let camera = camera else {
set(error: .cameraUnavailable)
status = .failed
return
}
let cameraInput: AVCaptureDeviceInput
do {
cameraInput = try AVCaptureDeviceInput(device: camera)
} catch {
set(error: .createCaptureInput(error))
status = .failed
return
}
guard session.canAddInput(cameraInput) else {
set(error: .cannotAddInput)
status = .failed
return
}
session.addInput(cameraInput)
if session.canAddOutput(videoOutput) {
session.addOutput(videoOutput)
videoOutput.videoSettings =
[kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
let videoConnection = videoOutput.connection(with: .video)
videoConnection?.videoOrientation = .portrait
} else {
set(error: .cannotAddOutput)
status = .failed
return
}
guard session.canAddOutput(photoOutput) else {
set(error: .cannotAddOutput)
status = .failed
return
}
session.addOutput(photoOutput)
let maxFormat = camera.formats
.reduce(into: []) { $0.append(contentsOf: $1.supportedMaxPhotoDimensions) }
.max { $0.width * $0.height < $1.width * $1.height }
if let maxFormat {
photoOutput.maxPhotoDimensions = maxFormat
}
//photoOutput.isHighResolutionCaptureEnabled = true
photoOutput.isDepthDataDeliveryEnabled = false
photoOutput.isLivePhotoCaptureEnabled = false
status = .configured
}
private func configure() {
checkPermissions()
sessionQueue.async {
self.configureCaptureSession()
self.session.startRunning()
}
}
func setVideoDelegate(_ delegate: AVCaptureVideoDataOutputSampleBufferDelegate,
queue: DispatchQueue) {
sessionQueue.async {
self.videoOutput.setSampleBufferDelegate(delegate, queue: queue)
}
}
func stopVideoCaptureSession() {
sessionQueue.async {
guard self.status == .configured else {
return
}
guard self.session.isRunning else {
return
}
self.session.stopRunning()
}
}
func startVideoCapture() {
guard status == .configured else {
return
}
sessionQueue.async {
guard !self.session.isRunning else {
return
}
self.session.startRunning()
}
}
// MARK: Photo Capture
func capturePhoto(delegate: AVCapturePhotoCaptureDelegate) {
sessionQueue.async {
let photoSettings = AVCapturePhotoSettings()
photoSettings.flashMode = .off
self.photoOutput.capturePhoto(with: photoSettings, delegate: delegate)
}
}
// MARK: Focus
func continuouslyFocusOnMiddle() {
guard let device = camera else {
return
}
do {
try device.lockForConfiguration()
if device.isFocusPointOfInterestSupported {
device.focusPointOfInterest = CGPoint(x: 0.5, y: 0.5)
device.focusMode = .continuousAutoFocus
}
print("Enabled continuous autofocus")
device.unlockForConfiguration()
} catch {
self.error("Could not lock device for configuration: \(error)")
}
}
}
extension CameraManager: Logger {
}