// // CameraManager.swift // XuXuCheck // // Created by henseycai on 2025/6/25. // import AVFoundation import CoreImage class CameraManager: NSObject { private let captureSession = AVCaptureSession() private var deviceInput: AVCaptureDeviceInput? private var videoOutput: AVCaptureVideoDataOutput? private let systemPreferredCamera = AVCaptureDevice.default(for: .video) private var sessionQueue = DispatchQueue(label: "video.preview.session") private var addToPreviewStream: ((CGImage) -> Void)? lazy var previewStream: AsyncStream = { AsyncStream { continuation in addToPreviewStream = { cgImage in continuation.yield(cgImage) } } }() private var isAuthorized: Bool { get async { let status = AVCaptureDevice.authorizationStatus(for: .video) // Determine if the user previously authorized camera access. var isAuthorized = status == .authorized // If the system hasn't determined the user's authorization status, // explicitly prompt them for approval. if status == .notDetermined { isAuthorized = await AVCaptureDevice.requestAccess(for: .video) } return isAuthorized } } override init() { super.init() Task { await configureSession() //await startSession() } } public func StartCapture(){ Task{ await startSession() } } public func StopCapture(){ Task{ await stopSession() } } private func startSession() async { guard await isAuthorized else { return } captureSession.startRunning() } private func stopSession() async { guard await isAuthorized else { return } captureSession.stopRunning() } private func configureSession() async { guard await isAuthorized, let systemPreferredCamera, let deviceInput = try? AVCaptureDeviceInput(device: systemPreferredCamera) else { return } captureSession.beginConfiguration() defer { self.captureSession.commitConfiguration() } let videoOutput = AVCaptureVideoDataOutput() videoOutput.setSampleBufferDelegate(self, queue: sessionQueue) guard captureSession.canAddInput(deviceInput) else { return } guard captureSession.canAddOutput(videoOutput) else { return } captureSession.addInput(deviceInput) captureSession.addOutput(videoOutput) //For a vertical orientation of the camera stream videoOutput.connection(with: .video)?.videoRotationAngle = 90 } private func rotate(by angle: CGFloat, from connection: AVCaptureConnection) { guard connection.isVideoRotationAngleSupported(angle) else { return } connection.videoRotationAngle = angle } } extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let currentFrame = sampleBuffer.cgImage else { print("Can't translate to CGImage") return } addToPreviewStream?(currentFrame) } } extension CMSampleBuffer { var cgImage: CGImage? { let pixelBuffer: CVPixelBuffer? = CMSampleBufferGetImageBuffer(self) guard let imagePixelBuffer = pixelBuffer else { return nil } return CIImage(cvPixelBuffer: imagePixelBuffer).cgImage } } extension CIImage { var cgImage: CGImage? { let ciContext = CIContext() guard let cgImage = ciContext.createCGImage(self, from: self.extent) else { return nil } return cgImage } }