fix: capture all system audio
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<AnyCancellable>()
|
||||
|
||||
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<AudioObjectID>
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user