fix: remove system audio capture toggle
This commit is contained in:
@@ -6,11 +6,10 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScreenCaptureKit
|
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 {
|
class AudioManager: NSObject, ObservableObject {
|
||||||
@Published var transcriptChunks: [TranscriptChunk] = []
|
@Published var transcriptChunks: [TranscriptChunk] = []
|
||||||
@Published var isRecording = false
|
@Published var isRecording = false
|
||||||
@Published var captureSystemAudio = true
|
|
||||||
|
|
||||||
private var audioEngine = AVAudioEngine()
|
private var audioEngine = AVAudioEngine()
|
||||||
private var micSocketTask: URLSessionWebSocketTask?
|
private var micSocketTask: URLSessionWebSocketTask?
|
||||||
@@ -26,15 +25,14 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
// First ensure everything is stopped and cleaned up
|
// First ensure everything is stopped and cleaned up
|
||||||
stopRecordingInternal()
|
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
|
// Start microphone capture in parallel with system audio
|
||||||
startMicrophoneTap()
|
self.startMicrophoneTap()
|
||||||
// Start system audio capture asynchronously
|
// Start system audio capture asynchronously
|
||||||
Task {
|
Task {
|
||||||
await startSystemAudioCapture()
|
await self.startSystemAudioCapture()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
startMicrophoneOnly()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,85 +63,71 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
print("Internal cleanup completed")
|
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)
|
/// Starts a microphone tap without creating a new Deepgram connection (used when also capturing system audio)
|
||||||
private func startMicrophoneTap() {
|
private func startMicrophoneTap() {
|
||||||
// Ensure audio engine is stopped and cleaned up
|
print("🎤 Starting microphone tap...")
|
||||||
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()
|
|
||||||
|
|
||||||
do {
|
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()
|
try audioEngine.start()
|
||||||
connectToDeepgram(source: .mic)
|
connectToDeepgram(source: .mic)
|
||||||
|
print("✅ Microphone tap started successfully")
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to start microphone tap: \(error)")
|
print("❌ Failed to start microphone tap: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cleanupAudioEngine() {
|
private func cleanupAudioEngine() {
|
||||||
|
print("🧹 Cleaning up audio engine...")
|
||||||
|
|
||||||
|
// Stop the engine first
|
||||||
if audioEngine.isRunning {
|
if audioEngine.isRunning {
|
||||||
audioEngine.stop()
|
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()
|
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 {
|
private func startSystemAudioCapture() async {
|
||||||
@@ -160,7 +144,6 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
|
|
||||||
guard let display = content.displays.first else {
|
guard let display = content.displays.first else {
|
||||||
print("❌ No display found")
|
print("❌ No display found")
|
||||||
startMicrophoneOnly()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,9 +186,6 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
if case SCStreamError.userDeclined = error {
|
if case SCStreamError.userDeclined = error {
|
||||||
print("📍 Permission denied. User needs to enable screen recording in System Settings.")
|
print("📍 Permission denied. User needs to enable screen recording in System Settings.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to microphone only
|
|
||||||
startMicrophoneOnly()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ class MeetingViewModel: ObservableObject {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
var captureSystemAudio: Bool {
|
|
||||||
get { audioManager.captureSystemAudio }
|
|
||||||
set { audioManager.captureSystemAudio = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
func startRecording() {
|
func startRecording() {
|
||||||
audioManager.startRecording()
|
audioManager.startRecording()
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ struct MeetingDetailView: View {
|
|||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
// Recording Controls
|
// Recording Controls
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
Toggle("Capture System Audio", isOn: $viewModel.captureSystemAudio)
|
|
||||||
.disabled(viewModel.isRecording)
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
viewModel.startRecording()
|
viewModel.startRecording()
|
||||||
|
|||||||
Reference in New Issue
Block a user