From 94d8469ebac2d043d929edb4a5cd0a6d99f228d8 Mon Sep 17 00:00:00 2001 From: superdooper86 Date: Wed, 22 Jul 2026 16:14:33 +0200 Subject: [PATCH] fix: capture all system audio --- Meetingnotes.xcodeproj/project.pbxproj | 8 +++---- meetingnotes/Managers/AudioManager.swift | 30 ++---------------------- meetingnotes/ProcessTap/ProcessTap.swift | 11 ++++----- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/Meetingnotes.xcodeproj/project.pbxproj b/Meetingnotes.xcodeproj/project.pbxproj index a96d641..b539b36 100644 --- a/Meetingnotes.xcodeproj/project.pbxproj +++ b/Meetingnotes.xcodeproj/project.pbxproj @@ -276,7 +276,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 24; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = G9LVHZAJNX; ENABLE_HARDENED_RUNTIME = YES; @@ -290,7 +290,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; ONLY_ACTIVE_ARCH = NO; OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI"; PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes; @@ -312,7 +312,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 24; + CURRENT_PROJECT_VERSION = 25; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = G9LVHZAJNX; ENABLE_HARDENED_RUNTIME = YES; @@ -326,7 +326,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI"; PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes; diff --git a/meetingnotes/Managers/AudioManager.swift b/meetingnotes/Managers/AudioManager.swift index 10b6df9..f9f2eb5 100644 --- a/meetingnotes/Managers/AudioManager.swift +++ b/meetingnotes/Managers/AudioManager.swift @@ -36,18 +36,14 @@ final class AudioManager: NSObject, ObservableObject { private var audioEngine = AVAudioEngine() private var sessionID = UUID() private var processTap: ProcessTap? - private let audioProcessController = AudioProcessController() private let permission = AudioRecordingPermission() private let tapQueue = DispatchQueue(label: "io.meetingnotes.audiotap", qos: .userInitiated) private let audioFileLock = NSLock() private var isTapActive = false - private var isRestartingSystemTap = false private var isAcceptingAudio = false private var micRetryCount = 0 private var pendingMicRestart: DispatchWorkItem? private let maxMicRetries = 3 - private var cancellables = Set() - private var micAudioFile: AVAudioFile? private var systemAudioFile: AVAudioFile? private var micAudioURL: URL? @@ -57,14 +53,6 @@ final class AudioManager: NSObject, ObservableObject { private override init() { super.init() observeAudioEngine() - audioProcessController.activate() - NSWorkspace.shared.publisher(for: \.runningApplications) - .debounce(for: .seconds(1), scheduler: RunLoop.main) - .sink { [weak self] _ in - guard let self, self.isTapActive else { return } - Task { await self.restartSystemAudioTapIfNeeded() } - } - .store(in: &cancellables) } deinit { @@ -304,8 +292,7 @@ final class AudioManager: NSObject, ObservableObject { return } - let processIDs = audioProcessController.processes.map(\.objectID) - let newTap = ProcessTap(target: .systemAudio(processObjectIDs: processIDs)) + let newTap = ProcessTap(target: .systemAudio) newTap.activate() if let tapError = newTap.errorMessage { errorMessage = "Failed to activate system audio capture: \(tapError)" @@ -329,21 +316,8 @@ final class AudioManager: NSObject, ObservableObject { } } - private func restartSystemAudioTapIfNeeded() async { - let next = Set(audioProcessController.processes.map(\.objectID)) - let current: Set - if case .systemAudio(let processIDs) = processTap?.target { - current = Set(processIDs) - } else { - current = [] - } - if next != current { await restartSystemAudioTap() } - } - private func restartSystemAudioTap() async { guard isRecording else { return } - isRestartingSystemTap = true - defer { isRestartingSystemTap = false } if isTapActive { processTap?.invalidate() processTap = nil @@ -380,7 +354,7 @@ final class AudioManager: NSObject, ObservableObject { source: .system ) } invalidationHandler: { [weak self] _ in - guard let self, !self.isRestartingSystemTap, self.isRecording else { return } + guard let self, self.isRecording else { return } Task { await self.restartSystemAudioTap() } } } diff --git a/meetingnotes/ProcessTap/ProcessTap.swift b/meetingnotes/ProcessTap/ProcessTap.swift index 66e0c3f..8755c88 100644 --- a/meetingnotes/ProcessTap/ProcessTap.swift +++ b/meetingnotes/ProcessTap/ProcessTap.swift @@ -5,7 +5,7 @@ import AVFoundation enum TapTarget { case singleProcess(AudioProcess) - case systemAudio(processObjectIDs: [AudioObjectID]) + case systemAudio var displayName: String { switch self { @@ -137,12 +137,9 @@ final class ProcessTap { case .singleProcess(let process): tapDescription = CATapDescription(stereoMixdownOfProcesses: [process.objectID]) logger.debug("Configuring tap for single process objectID: \(process.objectID)") - case .systemAudio(let processObjectIDs): - if processObjectIDs.isEmpty { - logger.warning("System audio tap configured with an empty list of processObjectIDs. This might not capture any audio or behave unexpectedly.") - } - tapDescription = CATapDescription(monoMixdownOfProcesses: processObjectIDs) - logger.debug("Configuring tap for system audio output using \(processObjectIDs.count) explicit processes.") + case .systemAudio: + tapDescription = CATapDescription(monoGlobalTapButExcludeProcesses: []) + logger.debug("Configuring a global system audio tap.") } tapDescription.uuid = UUID()