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:
parent
eb3e98d567
commit
d4a39dd7c3
@ -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 {
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,6 +162,12 @@ private func tapProcessCallback(
|
|||||||
|
|
||||||
// MARK: - PlayerRecorder
|
// MARK: - PlayerRecorder
|
||||||
|
|
||||||
|
/// 全局状态:录制是否正在 finalize(阻止应用退出)
|
||||||
|
enum RecorderState {
|
||||||
|
static nonisolated(unsafe) var isFinalizing: Bool = false
|
||||||
|
static nonisolated(unsafe) var pendingTerminate: Bool = false
|
||||||
|
}
|
||||||
|
|
||||||
/// 视频源录制器:AVPlayerItemVideoOutput 抓帧 + MTAudioProcessingTap 捕获音频
|
/// 视频源录制器:AVPlayerItemVideoOutput 抓帧 + MTAudioProcessingTap 捕获音频
|
||||||
/// 边录边写文件,录制结束后仅改名和移动
|
/// 边录边写文件,录制结束后仅改名和移动
|
||||||
@MainActor
|
@MainActor
|
||||||
@ -287,6 +293,7 @@ final class PlayerRecorder: NSObject {
|
|||||||
guard isRecording else { return }
|
guard isRecording else { return }
|
||||||
isRecording = false
|
isRecording = false
|
||||||
isRunning = false
|
isRunning = false
|
||||||
|
RecorderState.isFinalizing = true // 阻止应用退出,直到 finishWriting 完成
|
||||||
captureTimer?.invalidate()
|
captureTimer?.invalidate()
|
||||||
captureTimer = nil
|
captureTimer = nil
|
||||||
stopDurationTimer()
|
stopDurationTimer()
|
||||||
@ -314,8 +321,14 @@ final class PlayerRecorder: NSObject {
|
|||||||
writer?.finishWriting { [weak self] in
|
writer?.finishWriting { [weak self] in
|
||||||
NSLog("[Recorder] finishWriting callback invoked")
|
NSLog("[Recorder] finishWriting callback invoked")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
RecorderState.isFinalizing = false // 允许应用退出了
|
||||||
|
|
||||||
guard let self = self, let w = self.writer else {
|
guard let self = self, let w = self.writer else {
|
||||||
NSLog("[Recorder] ⚠️ self or writer is nil in finishWriting callback")
|
NSLog("[Recorder] ⚠️ self or writer is nil in finishWriting callback")
|
||||||
|
if RecorderState.pendingTerminate {
|
||||||
|
RecorderState.pendingTerminate = false
|
||||||
|
NSApp.reply(toApplicationShouldTerminate: true)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let vFrames = self.captureFrameCount
|
let vFrames = self.captureFrameCount
|
||||||
@ -333,6 +346,7 @@ final class PlayerRecorder: NSObject {
|
|||||||
} else {
|
} else {
|
||||||
NSLog("[Recorder] ⚠️ Temp file missing: %@", tempURL.path)
|
NSLog("[Recorder] ⚠️ Temp file missing: %@", tempURL.path)
|
||||||
self.onError?("Recording file missing")
|
self.onError?("Recording file missing")
|
||||||
|
self.checkPendingTerminate()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NSLog("[Recorder] ✗ finishWriting failed: status=%d, error=%@",
|
NSLog("[Recorder] ✗ finishWriting failed: status=%d, error=%@",
|
||||||
@ -341,6 +355,7 @@ final class PlayerRecorder: NSObject {
|
|||||||
if let url = self.tempURL {
|
if let url = self.tempURL {
|
||||||
try? FileManager.default.removeItem(at: url)
|
try? FileManager.default.removeItem(at: url)
|
||||||
}
|
}
|
||||||
|
self.checkPendingTerminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -632,7 +647,13 @@ final class PlayerRecorder: NSObject {
|
|||||||
|
|
||||||
panel.begin { [weak self] response in
|
panel.begin { [weak self] response in
|
||||||
Task { @MainActor 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 {
|
if response == .OK, let finalURL = panel.url {
|
||||||
do {
|
do {
|
||||||
@ -653,9 +674,18 @@ final class PlayerRecorder: NSObject {
|
|||||||
NSLog("[Recorder] User cancelled save, deleting temp file")
|
NSLog("[Recorder] User cancelled save, deleting temp file")
|
||||||
try? FileManager.default.removeItem(at: tempURL)
|
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
|
#endif
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user