fix: remove system audio capture toggle

This commit is contained in:
Owen Gretzinger
2025-07-10 17:40:22 -04:00
parent 619223fa1e
commit 2ca102f8d6
3 changed files with 55 additions and 81 deletions
+54 -74
View File
@@ -6,11 +6,10 @@ import Foundation
import SwiftUI
import ScreenCaptureKit
/// Manages audio capture from microphone and/or system audio and handles real-time transcription via Deepgram
/// Manages audio capture from microphone and system audio and handles real-time transcription via Deepgram
class AudioManager: NSObject, ObservableObject {
@Published var transcriptChunks: [TranscriptChunk] = []
@Published var isRecording = false
@Published var captureSystemAudio = true
private var audioEngine = AVAudioEngine()
private var micSocketTask: URLSessionWebSocketTask?
@@ -26,15 +25,14 @@ class AudioManager: NSObject, ObservableObject {
// First ensure everything is stopped and cleaned up
stopRecordingInternal()
if captureSystemAudio {
// Add a small delay to ensure cleanup is complete
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// Start microphone capture in parallel with system audio
startMicrophoneTap()
self.startMicrophoneTap()
// Start system audio capture asynchronously
Task {
await startSystemAudioCapture()
await self.startSystemAudioCapture()
}
} else {
startMicrophoneOnly()
}
}
@@ -65,85 +63,71 @@ class AudioManager: NSObject, ObservableObject {
print("Internal cleanup completed")
}
private func startMicrophoneOnly() {
print("Starting microphone-only recording...")
// Ensure audio engine is stopped and cleaned up
cleanupAudioEngine()
// Set up audio engine for microphone capture
let inputNode = audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
// Convert to the format Deepgram expects: 16kHz, 16-bit, mono
let targetFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
sampleRate: 16000,
channels: 1,
interleaved: false)!
let converter = AVAudioConverter(from: recordingFormat, to: targetFormat)!
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] (buffer, time) in
self?.processAudioBuffer(buffer, converter: converter, targetFormat: targetFormat, source: .mic)
}
audioEngine.prepare()
do {
try audioEngine.start()
DispatchQueue.main.async {
self.isRecording = true
}
connectToDeepgram(source: .mic)
print("Microphone recording started")
} catch {
print("Failed to start audio engine: \(error)")
}
}
/// Starts a microphone tap without creating a new Deepgram connection (used when also capturing system audio)
private func startMicrophoneTap() {
// Ensure audio engine is stopped and cleaned up
cleanupAudioEngine()
let inputNode = audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
let targetFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
sampleRate: 16000,
channels: 1,
interleaved: false)!
let converter = AVAudioConverter(from: recordingFormat, to: targetFormat)!
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
// Debug mic RMS
if let ch = buffer.floatChannelData?[0] {
let frameCount = Int(buffer.frameLength)
let samples = UnsafeBufferPointer(start: ch, count: frameCount)
let rms = sqrt(samples.map { $0 * $0 }.reduce(0, +) / Float(frameCount))
print("🎤 Mic RMS: \(rms)")
}
self?.processAudioBuffer(buffer, converter: converter, targetFormat: targetFormat, source: .mic)
}
audioEngine.prepare()
print("🎤 Starting microphone tap...")
do {
let inputNode = audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
guard let targetFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
sampleRate: 16000,
channels: 1,
interleaved: false) else {
print("❌ Failed to create target audio format for mic tap")
return
}
guard let converter = AVAudioConverter(from: recordingFormat, to: targetFormat) else {
print("❌ Failed to create audio converter for mic tap")
return
}
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
// Debug mic RMS
if let ch = buffer.floatChannelData?[0] {
let frameCount = Int(buffer.frameLength)
let samples = UnsafeBufferPointer(start: ch, count: frameCount)
let rms = sqrt(samples.map { $0 * $0 }.reduce(0, +) / Float(frameCount))
print("🎤 Mic RMS: \(rms)")
}
self?.processAudioBuffer(buffer, converter: converter, targetFormat: targetFormat, source: .mic)
}
audioEngine.prepare()
try audioEngine.start()
connectToDeepgram(source: .mic)
print("✅ Microphone tap started successfully")
} catch {
print("Failed to start microphone tap: \(error)")
print("Failed to start microphone tap: \(error)")
}
}
private func cleanupAudioEngine() {
print("🧹 Cleaning up audio engine...")
// Stop the engine first
if audioEngine.isRunning {
audioEngine.stop()
print("⏹️ Audio engine stopped")
}
// Reset the audio engine - this safely removes all taps
// Remove any existing taps on the input node
let inputNode = audioEngine.inputNode
inputNode.removeTap(onBus: 0)
print("🔇 Input tap removed")
// Reset the audio engine - this removes all connections and taps
audioEngine.reset()
print("🔄 Audio engine reset")
// Create a fresh audio engine to ensure clean state
audioEngine = AVAudioEngine()
print("✨ Fresh audio engine created")
}
private func startSystemAudioCapture() async {
@@ -160,7 +144,6 @@ class AudioManager: NSObject, ObservableObject {
guard let display = content.displays.first else {
print("❌ No display found")
startMicrophoneOnly()
return
}
@@ -203,9 +186,6 @@ class AudioManager: NSObject, ObservableObject {
if case SCStreamError.userDeclined = error {
print("📍 Permission denied. User needs to enable screen recording in System Settings.")
}
// Fallback to microphone only
startMicrophoneOnly()
}
}
+1 -4
View File
@@ -30,10 +30,7 @@ class MeetingViewModel: ObservableObject {
.store(in: &cancellables)
}
var captureSystemAudio: Bool {
get { audioManager.captureSystemAudio }
set { audioManager.captureSystemAudio = newValue }
}
func startRecording() {
audioManager.startRecording()
-3
View File
@@ -39,9 +39,6 @@ struct MeetingDetailView: View {
VStack(spacing: 20) {
// Recording Controls
VStack(spacing: 12) {
Toggle("Capture System Audio", isOn: $viewModel.captureSystemAudio)
.disabled(viewModel.isRecording)
HStack {
Button {
viewModel.startRecording()