diff --git a/Sources/MiniPlayerApp.swift b/Sources/MiniPlayerApp.swift index dfcb039..5cc5a03 100644 --- a/Sources/MiniPlayerApp.swift +++ b/Sources/MiniPlayerApp.swift @@ -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 } diff --git a/Sources/PlayerRecorder.swift b/Sources/PlayerRecorder.swift index 7f7390d..6ed9a26 100644 --- a/Sources/PlayerRecorder.swift +++ b/Sources/PlayerRecorder.swift @@ -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