diff --git a/macamera/FrameView/FrameHandler.swift b/macamera/FrameView/FrameHandler.swift index e69de29..b2f371d 100644 --- a/macamera/FrameView/FrameHandler.swift +++ b/macamera/FrameView/FrameHandler.swift @@ -0,0 +1,84 @@ +// +// FrameHandler.swift +// macamera +// +// Created by Maxime on 06/05/2025. +// + +// Original Repository : https://github.com/daved01/LiveCameraSwiftUI/tree/main + +import AVFoundation +import CoreImage + +class FrameHandler: NSObject, ObservableObject { + + @Published var frame: CGImage? + + private var permissionGranted: Bool = true + private let captureSession = AVCaptureSession() + private let context = CIContext() + + override init() { + super.init() + + Task.detached(priority: .background) { + self.permissionGranted = await self.checkPermission() + self.setupCaptureSession() + } + } +} + +// MARK: - Convenience Methods + +extension FrameHandler { + + private func checkPermission() async -> Bool { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .notDetermined: + return await AVCaptureDevice.requestAccess(for: .video) + case .authorized: + return true + default: + return false + } + } + + private func setupCaptureSession() { + let videoOutput = AVCaptureVideoDataOutput() + + guard permissionGranted else { return } + guard let videoDevice = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: .back) else { return } + guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else { return } + + guard captureSession.canAddInput(videoDeviceInput) else { return } + captureSession.addInput(videoDeviceInput) + + videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "sampleBufferQueue")) + captureSession.addOutput(videoOutput) + + videoOutput.connection(with: .video)?.videoRotationAngle = 90 + + captureSession.startRunning() + } +} + +// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate + +extension FrameHandler: AVCaptureVideoDataOutputSampleBufferDelegate { + + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + guard let cgImage = imageFromSampleBuffer(sampleBuffer: sampleBuffer) else { return } + + // All UI updates should be/ must be performed on the main queue. + DispatchQueue.main.async { [unowned self] in + self.frame = cgImage + } + } + + private func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> CGImage? { + guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil } + let ciImage = CIImage(cvPixelBuffer: imageBuffer) + guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil } + return cgImage + } +}