diff --git a/Sources/StreamRecorderKit/FFmpegRecorder.swift b/Sources/StreamRecorderKit/FFmpegRecorder.swift index 7901a49..52e036d 100644 --- a/Sources/StreamRecorderKit/FFmpegRecorder.swift +++ b/Sources/StreamRecorderKit/FFmpegRecorder.swift @@ -15,6 +15,21 @@ final class ProgressState: @unchecked Sendable { } } +/// Thread-safe atomic boolean for one-shot guards +final class AtomicBool: @unchecked Sendable { + private var value = false + private let lock = NSLock() + + /// Returns true if this call set it from false to true (first caller wins) + func setTrue() -> Bool { + lock.lock() + defer { lock.unlock() } + guard !value else { return false } + value = true + return true + } +} + public final class FFmpegRecorder: StreamRecorderEngine { public weak var delegate: StreamRecorderDelegate? public private(set) var isRecording = false @@ -108,6 +123,7 @@ public final class FFmpegRecorder: StreamRecorderEngine { // 异步等待进程完成(不阻塞 cooperative thread) await waitForProcess(proc) + NSLog("[Recorder] ffmpeg process exited, status: %d", proc.terminationStatus) stderrPipe.fileHandleForReading.readabilityHandler = nil isRecording = false @@ -119,9 +135,11 @@ public final class FFmpegRecorder: StreamRecorderEngine { throw RecordingError.outputNotFound } + NSLog("[Recorder] Analyzing output file...") // 分析输出文件 let ffprobePath = ffmpegPath.replacingOccurrences(of: "ffmpeg", with: "ffprobe") let result = try await analyzeOutput(path: outputPath, ffprobePath: ffprobePath) + NSLog("[Recorder] Analysis complete, notifying delegate") delegate?.recorder(self, didFinishWithResult: result) } @@ -141,13 +159,18 @@ public final class FFmpegRecorder: StreamRecorderEngine { private func waitForProcess(_ proc: Process) async { guard proc.isRunning else { return } await withCheckedContinuation { (continuation: CheckedContinuation) in + let resumed = AtomicBool() proc.terminationHandler = { _ in - continuation.resume() + if resumed.setTrue() { + continuation.resume() + } } // Race condition: process may have exited between isRunning check and setting handler if !proc.isRunning { proc.terminationHandler = nil - continuation.resume() + if resumed.setTrue() { + continuation.resume() + } } } }