diff --git a/SousChefAI/Services/CameraManager.swift b/SousChefAI/Services/CameraManager.swift index 88eec15..ed2ad77 100644 --- a/SousChefAI/Services/CameraManager.swift +++ b/SousChefAI/Services/CameraManager.swift @@ -18,6 +18,16 @@ final class CameraManager: NSObject, ObservableObject { @Published var error: CameraError? @Published var isRunning = false + enum SessionState { + case idle + case configuring + case configured + case starting + case running + case stopping + } + @Published private(set) var sessionState: SessionState = .idle + nonisolated(unsafe) private let captureSession = AVCaptureSession() nonisolated(unsafe) private var videoOutput: AVCaptureVideoDataOutput? private let videoQueue = DispatchQueue(label: "com.souschef.video", qos: .userInitiated) @@ -55,13 +65,22 @@ final class CameraManager: NSObject, ObservableObject { // MARK: - Session Setup func setupSession() async throws { - print("🎥 CameraManager.setupSession() - STARTED at \(Date())") + print("🎥 CameraManager.setupSession() - STARTED at \(Date()), current state: \(sessionState)") + + // Wait if session is stopping + if sessionState == .stopping { + print("🎥 CameraManager.setupSession() - ⏳ Waiting for session to finish stopping...") + await waitForSessionToStop() + } + // Only configure once guard !isConfigured else { print("🎥 CameraManager.setupSession() - Already configured, returning") return } + sessionState = .configuring + // Ensure authorization is checked first await checkAuthorization() @@ -112,13 +131,20 @@ final class CameraManager: NSObject, ObservableObject { captureSession.commitConfiguration() isConfigured = true + sessionState = .configured print("🎥 CameraManager.setupSession() - ✅ COMPLETED successfully at \(Date())") } // MARK: - Session Control func startSession() { - guard !captureSession.isRunning else { return } + guard !captureSession.isRunning else { + print("🎥 CameraManager.startSession() - Session already running") + return + } + + print("🎥 CameraManager.startSession() - Starting session") + sessionState = .starting let session = captureSession Task.detached { [weak self] in @@ -126,12 +152,20 @@ final class CameraManager: NSObject, ObservableObject { await MainActor.run { [weak self] in self?.isRunning = true + self?.sessionState = .running + print("🎥 CameraManager.startSession() - ✅ Session running") } } } func stopSession() { - guard captureSession.isRunning else { return } + guard captureSession.isRunning else { + print("🎥 CameraManager.stopSession() - Session not running") + return + } + + print("🎥 CameraManager.stopSession() - Stopping session") + sessionState = .stopping let session = captureSession Task.detached { [weak self] in @@ -139,10 +173,36 @@ final class CameraManager: NSObject, ObservableObject { await MainActor.run { [weak self] in self?.isRunning = false + self?.sessionState = .idle + print("🎥 CameraManager.stopSession() - ✅ Session stopped") } } } + // MARK: - Helper Methods + + private func waitForSessionToStop() async { + // Wait for session to reach idle state + while sessionState == .stopping { + try? await Task.sleep(for: .milliseconds(100)) + } + print("🎥 CameraManager.waitForSessionToStop() - ✅ Session is now idle") + } + + /// Cleanup method that ensures session is fully stopped before returning + func cleanup() async { + print("🎥 CameraManager.cleanup() - Starting cleanup") + + if captureSession.isRunning { + stopSession() + } + + // Wait for session to fully stop + await waitForSessionToStop() + + print("🎥 CameraManager.cleanup() - ✅ Cleanup complete") + } + // MARK: - Frame Stream func frameStream() -> AsyncStream { diff --git a/SousChefAI/ViewModels/ScannerViewModel.swift b/SousChefAI/ViewModels/ScannerViewModel.swift index bbe64f1..909835d 100644 --- a/SousChefAI/ViewModels/ScannerViewModel.swift +++ b/SousChefAI/ViewModels/ScannerViewModel.swift @@ -175,8 +175,10 @@ final class ScannerViewModel: ObservableObject { // MARK: - Cleanup - func cleanup() { + func cleanup() async { + print("📱 ScannerViewModel.cleanup() - Starting cleanup") stopScanning() - stopCamera() + await cameraManager.cleanup() + print("📱 ScannerViewModel.cleanup() - ✅ Cleanup complete") } } diff --git a/SousChefAI/Views/ScannerView.swift b/SousChefAI/Views/ScannerView.swift index 8247e22..85c88b0 100644 --- a/SousChefAI/Views/ScannerView.swift +++ b/SousChefAI/Views/ScannerView.swift @@ -96,7 +96,9 @@ struct ScannerView: View { } .onDisappear { print("🔵 ScannerView.onDisappear - Cleaning up at \(Date())") - viewModel.cleanup() + Task { + await viewModel.cleanup() + } } .alert("Camera Error", isPresented: .constant(viewModel.error != nil)) { Button("OK") {