diff --git a/speaktype/Services/AudioRecordingService.swift b/speaktype/Services/AudioRecordingService.swift index a164039..8236000 100644 --- a/speaktype/Services/AudioRecordingService.swift +++ b/speaktype/Services/AudioRecordingService.swift @@ -29,6 +29,7 @@ class AudioRecordingService: NSObject, ObservableObject { private var isSessionStarted = false private var setupTask: Task? private var isStopping = false // Flag to prevent appending during stop + private var idleSessionStopWorkItem: DispatchWorkItem? // MARK: - Chunking state private var chunkAssetWriter: AVAssetWriter? @@ -184,6 +185,19 @@ class AudioRecordingService: NSObject, ObservableObject { // Give it a moment to fully initialize Thread.sleep(forTimeInterval: 0.3) print("🎤 Audio capture session ready") + self.scheduleIdleSessionStop() + } + } + + /// Stop the prewarmed capture session when the recorder is no longer visible. + func stopSessionIfIdle() { + audioQueue.async { + self.cancelIdleSessionStop() + guard !self.isRecording, let session = self.captureSession, session.isRunning else { + return + } + print("🎤 Stopping idle audio capture session") + session.stopRunning() } } @@ -199,6 +213,7 @@ class AudioRecordingService: NSObject, ObservableObject { resetMainWriterState() resetChunkWriterState() isRecording = true + cancelIdleSessionStop() // 2. Wrap setup in a Task so stopRecording can wait for it setupTask = Task { @MainActor in @@ -353,6 +368,7 @@ class AudioRecordingService: NSObject, ObservableObject { finishGroup.notify(queue: self.audioQueue) { // Keep microphone fully idle outside active recordings. + self.cancelIdleSessionStop() self.captureSession?.stopRunning() self.isStopping = false self.shouldDiscardCurrentRecordingOutput = false @@ -411,6 +427,27 @@ class AudioRecordingService: NSObject, ObservableObject { return chunksDir } + + private func scheduleIdleSessionStop(delay: TimeInterval = 8) { + cancelIdleSessionStop() + + let work = DispatchWorkItem { [weak self] in + guard let self = self else { return } + guard !self.isRecording, let session = self.captureSession, session.isRunning else { + return + } + print("🎤 Auto-stopping prewarmed session to save resources") + session.stopRunning() + } + + idleSessionStopWorkItem = work + audioQueue.asyncAfter(deadline: .now() + delay, execute: work) + } + + private func cancelIdleSessionStop() { + idleSessionStopWorkItem?.cancel() + idleSessionStopWorkItem = nil + } } extension AudioRecordingService: AVCaptureAudioDataOutputSampleBufferDelegate { diff --git a/speaktype/Views/Overlays/MiniRecorderView.swift b/speaktype/Views/Overlays/MiniRecorderView.swift index a006456..b57d4a5 100644 --- a/speaktype/Views/Overlays/MiniRecorderView.swift +++ b/speaktype/Views/Overlays/MiniRecorderView.swift @@ -230,6 +230,7 @@ struct MiniRecorderView: View { if let localEscapeMonitor = localEscapeMonitor { NSEvent.removeMonitor(localEscapeMonitor) } + audioRecorder.stopSessionIfIdle() } .onChange(of: isListening) { // Only animate when actually recording to save CPU