fixed race condition

This commit is contained in:
2026-02-15 14:39:22 -06:00
parent 55b48575cc
commit 3bfd4a6f4c
3 changed files with 70 additions and 6 deletions

View File

@@ -18,6 +18,16 @@ final class CameraManager: NSObject, ObservableObject {
@Published var error: CameraError? @Published var error: CameraError?
@Published var isRunning = false @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 let captureSession = AVCaptureSession()
nonisolated(unsafe) private var videoOutput: AVCaptureVideoDataOutput? nonisolated(unsafe) private var videoOutput: AVCaptureVideoDataOutput?
private let videoQueue = DispatchQueue(label: "com.souschef.video", qos: .userInitiated) private let videoQueue = DispatchQueue(label: "com.souschef.video", qos: .userInitiated)
@@ -55,13 +65,22 @@ final class CameraManager: NSObject, ObservableObject {
// MARK: - Session Setup // MARK: - Session Setup
func setupSession() async throws { 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 // Only configure once
guard !isConfigured else { guard !isConfigured else {
print("🎥 CameraManager.setupSession() - Already configured, returning") print("🎥 CameraManager.setupSession() - Already configured, returning")
return return
} }
sessionState = .configuring
// Ensure authorization is checked first // Ensure authorization is checked first
await checkAuthorization() await checkAuthorization()
@@ -112,13 +131,20 @@ final class CameraManager: NSObject, ObservableObject {
captureSession.commitConfiguration() captureSession.commitConfiguration()
isConfigured = true isConfigured = true
sessionState = .configured
print("🎥 CameraManager.setupSession() - ✅ COMPLETED successfully at \(Date())") print("🎥 CameraManager.setupSession() - ✅ COMPLETED successfully at \(Date())")
} }
// MARK: - Session Control // MARK: - Session Control
func startSession() { 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 let session = captureSession
Task.detached { [weak self] in Task.detached { [weak self] in
@@ -126,12 +152,20 @@ final class CameraManager: NSObject, ObservableObject {
await MainActor.run { [weak self] in await MainActor.run { [weak self] in
self?.isRunning = true self?.isRunning = true
self?.sessionState = .running
print("🎥 CameraManager.startSession() - ✅ Session running")
} }
} }
} }
func stopSession() { 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 let session = captureSession
Task.detached { [weak self] in Task.detached { [weak self] in
@@ -139,10 +173,36 @@ final class CameraManager: NSObject, ObservableObject {
await MainActor.run { [weak self] in await MainActor.run { [weak self] in
self?.isRunning = false 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 // MARK: - Frame Stream
func frameStream() -> AsyncStream<CVPixelBuffer> { func frameStream() -> AsyncStream<CVPixelBuffer> {

View File

@@ -175,8 +175,10 @@ final class ScannerViewModel: ObservableObject {
// MARK: - Cleanup // MARK: - Cleanup
func cleanup() { func cleanup() async {
print("📱 ScannerViewModel.cleanup() - Starting cleanup")
stopScanning() stopScanning()
stopCamera() await cameraManager.cleanup()
print("📱 ScannerViewModel.cleanup() - ✅ Cleanup complete")
} }
} }

View File

@@ -96,7 +96,9 @@ struct ScannerView: View {
} }
.onDisappear { .onDisappear {
print("🔵 ScannerView.onDisappear - Cleaning up at \(Date())") print("🔵 ScannerView.onDisappear - Cleaning up at \(Date())")
viewModel.cleanup() Task {
await viewModel.cleanup()
}
} }
.alert("Camera Error", isPresented: .constant(viewModel.error != nil)) { .alert("Camera Error", isPresented: .constant(viewModel.error != nil)) {
Button("OK") { Button("OK") {