fix: block app termination while recording is finalizing

- RecorderState.isFinalizing flag set during stopRecording → finishWriting
- applicationShouldTerminate returns .terminateLater if finalizing
- Shows alert '正在保存录制文件...' to inform user
- After save dialog completes, calls NSApp.reply(toApplicationShouldTerminate: true)
- All failure paths also check and resolve pendingTerminate
- Fixes: 9-min recording lost because user closed app before async finishWriting completed
This commit is contained in:
yumoqing 2026-06-25 21:27:02 +08:00
parent eb3e98d567
commit d4a39dd7c3
2 changed files with 47 additions and 1 deletions

View File

@ -28,6 +28,22 @@ class AppDel: NSObject, NSApplicationDelegate {
}
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
if RecorderState.isFinalizing {
NSLog("[MiniPlayer] ⏳ Termination blocked: recording is finalizing")
RecorderState.pendingTerminate = true
//
let alert = NSAlert()
alert.messageText = "正在保存录制文件..."
alert.informativeText = "请等待录制保存完成后再关闭应用。"
alert.alertStyle = .informational
alert.addButton(withTitle: "确定")
alert.runModal()
return .terminateLater
}
return .terminateNow
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}

View File

@ -162,6 +162,12 @@ private func tapProcessCallback(
// MARK: - PlayerRecorder
/// finalize退
enum RecorderState {
static nonisolated(unsafe) var isFinalizing: Bool = false
static nonisolated(unsafe) var pendingTerminate: Bool = false
}
/// AVPlayerItemVideoOutput + MTAudioProcessingTap
///
@MainActor
@ -287,6 +293,7 @@ final class PlayerRecorder: NSObject {
guard isRecording else { return }
isRecording = false
isRunning = false
RecorderState.isFinalizing = true // 退 finishWriting
captureTimer?.invalidate()
captureTimer = nil
stopDurationTimer()
@ -314,8 +321,14 @@ final class PlayerRecorder: NSObject {
writer?.finishWriting { [weak self] in
NSLog("[Recorder] finishWriting callback invoked")
DispatchQueue.main.async {
RecorderState.isFinalizing = false // 退
guard let self = self, let w = self.writer else {
NSLog("[Recorder] ⚠️ self or writer is nil in finishWriting callback")
if RecorderState.pendingTerminate {
RecorderState.pendingTerminate = false
NSApp.reply(toApplicationShouldTerminate: true)
}
return
}
let vFrames = self.captureFrameCount
@ -333,6 +346,7 @@ final class PlayerRecorder: NSObject {
} else {
NSLog("[Recorder] ⚠️ Temp file missing: %@", tempURL.path)
self.onError?("Recording file missing")
self.checkPendingTerminate()
}
} else {
NSLog("[Recorder] ✗ finishWriting failed: status=%d, error=%@",
@ -341,6 +355,7 @@ final class PlayerRecorder: NSObject {
if let url = self.tempURL {
try? FileManager.default.removeItem(at: url)
}
self.checkPendingTerminate()
}
}
}
@ -632,7 +647,13 @@ final class PlayerRecorder: NSObject {
panel.begin { [weak self] response in
Task { @MainActor in
guard let self = self else { return }
guard let self = self else {
if RecorderState.pendingTerminate {
RecorderState.pendingTerminate = false
NSApp.reply(toApplicationShouldTerminate: true)
}
return
}
if response == .OK, let finalURL = panel.url {
do {
@ -653,9 +674,18 @@ final class PlayerRecorder: NSObject {
NSLog("[Recorder] User cancelled save, deleting temp file")
try? FileManager.default.removeItem(at: tempURL)
}
self.checkPendingTerminate()
}
}
}
private func checkPendingTerminate() {
if RecorderState.pendingTerminate {
RecorderState.pendingTerminate = false
NSLog("[MiniPlayer] Pending terminate triggered, replying to terminate")
NSApp.reply(toApplicationShouldTerminate: true)
}
}
}
#endif