fix: window close exits app + save dialog diagnostics

- AppDel: use NSWindow.willCloseNotification observer with visibility check
  instead of broken applicationShouldTerminateAfterLastWindowClosed logic
  (SwiftUI WindowGroup keeps hidden windows that blocked termination)
- finishWriting callback: use DispatchQueue.main.async instead of Task @MainActor
  (more reliable from non-main-thread AVFoundation callback)
- Add extensive NSLog diagnostics: callback invoked, file existence check,
  file size, status codes, nil checks
This commit is contained in:
yumoqing 2026-06-25 21:08:59 +08:00
parent cf53afd7c8
commit eb3e98d567
2 changed files with 46 additions and 7 deletions

View File

@ -5,11 +5,37 @@ import SwiftBricks
#if os(macOS) #if os(macOS)
import AppKit import AppKit
// 退 NSPanel退 // 退
class AppDel: NSObject, NSApplicationDelegate { class AppDel: NSObject, NSApplicationDelegate {
private var closeObserver: Any?
func applicationDidFinishLaunching(_ notification: Notification) {
//
closeObserver = NotificationCenter.default.addObserver(
forName: NSWindow.willCloseNotification,
object: nil,
queue: .main
) { _ in
//
DispatchQueue.main.async {
let nonPanelWindows = NSApp.windows.filter { !($0 is NSPanel) && $0.isVisible }
NSLog("[MiniPlayer] Window closed. Visible non-panel windows: %d", nonPanelWindows.count)
if nonPanelWindows.isEmpty {
NSLog("[MiniPlayer] No visible main windows, terminating app")
NSApp.terminate(nil)
}
}
}
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
// NSPanel退 return true
return !sender.windows.contains { !($0 is NSPanel) } }
deinit {
if let obs = closeObserver {
NotificationCenter.default.removeObserver(obs)
}
} }
} }
#endif #endif

View File

@ -312,8 +312,12 @@ final class PlayerRecorder: NSObject {
audioInput?.markAsFinished() audioInput?.markAsFinished()
writer?.finishWriting { [weak self] in writer?.finishWriting { [weak self] in
Task { @MainActor in NSLog("[Recorder] finishWriting callback invoked")
guard let self = self, let w = self.writer else { return } DispatchQueue.main.async {
guard let self = self, let w = self.writer else {
NSLog("[Recorder] ⚠️ self or writer is nil in finishWriting callback")
return
}
let vFrames = self.captureFrameCount let vFrames = self.captureFrameCount
NSLog("[Recorder] finishWriting: status=%d, error=%@, videoFrames=%d, audioFrames=%d", NSLog("[Recorder] finishWriting: status=%d, error=%@, videoFrames=%d, audioFrames=%d",
w.status.rawValue, w.status.rawValue,
@ -321,9 +325,18 @@ final class PlayerRecorder: NSObject {
vFrames, totalAudioFrames) vFrames, totalAudioFrames)
if w.status == .completed, let tempURL = self.tempURL { if w.status == .completed, let tempURL = self.tempURL {
// //
self.showSaveDialog(tempURL: tempURL) if FileManager.default.fileExists(atPath: tempURL.path) {
let size = (try? FileManager.default.attributesOfItem(atPath: tempURL.path)[.size] as? UInt64) ?? 0
NSLog("[Recorder] ✓ Temp file exists: %@, size=%llu bytes", tempURL.lastPathComponent, size)
self.showSaveDialog(tempURL: tempURL)
} else {
NSLog("[Recorder] ⚠️ Temp file missing: %@", tempURL.path)
self.onError?("Recording file missing")
}
} else { } else {
NSLog("[Recorder] ✗ finishWriting failed: status=%d, error=%@",
w.status.rawValue, w.error?.localizedDescription ?? "unknown")
self.onError?("Recording failed: \(w.error?.localizedDescription ?? "unknown (status=\(w.status.rawValue))")") self.onError?("Recording failed: \(w.error?.localizedDescription ?? "unknown (status=\(w.status.rawValue))")")
if let url = self.tempURL { if let url = self.tempURL {
try? FileManager.default.removeItem(at: url) try? FileManager.default.removeItem(at: url)