fd7e1a5907
AudioRecorder captures system audio (ScreenCaptureKit) + mic (AVAudioEngine) on a single serial ioQueue, one shared monotonic t0, time-driven writers (pad gaps / trim overlaps) so tracks stay aligned, and an energy mic-VAD for 'self' spans. AudioMixer sums the aligned tracks into mixed_mono_16k.wav. SessionController drives a serialized start/stop state machine, writes the session folder + self_vad.json, exposes live level meters, and finalizes on quit. Hardening from review: ioQueue single-domain (no races), stop() never hangs (mic-first teardown + bounded stopCapture), layout-agnostic mic deep-copy, discard-only video output to keep SCStream alive, VAD lockstep on committed frames, stable signing team in project.yml, single-instance enforcement.
36 lines
1.5 KiB
Swift
36 lines
1.5 KiB
Swift
import AppKit
|
|
|
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
// Run as a menu-bar accessory (no Dock icon, no main window).
|
|
// LSUIElement in Info.plist already enforces this; set it explicitly too
|
|
// so behavior is unambiguous regardless of how the app is launched.
|
|
NSApp.setActivationPolicy(.accessory)
|
|
terminateOtherInstances()
|
|
}
|
|
|
|
/// Single-instance: a fresh launch (e.g. each Xcode ⌘R) terminates any older
|
|
/// copies so you never end up with two menu-bar icons.
|
|
private func terminateOtherInstances() {
|
|
guard let bundleID = Bundle.main.bundleIdentifier else { return }
|
|
let me = NSRunningApplication.current.processIdentifier
|
|
for app in NSRunningApplication.runningApplications(withBundleIdentifier: bundleID)
|
|
where app.processIdentifier != me {
|
|
app.terminate()
|
|
}
|
|
}
|
|
|
|
/// If a recording is in progress when the user quits, finalize it (flush WAV
|
|
/// headers) before the process exits, so the session isn't corrupted.
|
|
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
|
guard let controller = SessionController.shared, controller.isBusy else {
|
|
return .terminateNow
|
|
}
|
|
Task { @MainActor in
|
|
await controller.prepareForTermination()
|
|
NSApp.reply(toApplicationShouldTerminate: true)
|
|
}
|
|
return .terminateLater
|
|
}
|
|
}
|