Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions speaktype/Services/AudioRecordingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class AudioRecordingService: NSObject, ObservableObject {
private var isSessionStarted = false
private var setupTask: Task<Void, Never>?
private var isStopping = false // Flag to prevent appending during stop
private var idleSessionStopWorkItem: DispatchWorkItem?

// MARK: - Chunking state
private var chunkAssetWriter: AVAssetWriter?
Expand Down Expand Up @@ -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()
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions speaktype/Views/Overlays/MiniRecorderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down