import Foundation
import CoreMedia
/// Abstracts the video sink so the orchestration loop is testable without AVFoundation.
protocol ClipRecorder {
func begin() throws
func appendFrame(index: Int, pts: CMTime) throws
func finish() async throws -> URL
}
/// Deterministic offline append loop: render frame n, append it at n/fps, report
/// progress. On cancel it stops appending but still finalizes the file cleanly.
func runClip(frameCount: Int, fps: Int, recorder: ClipRecorder,
renderFrame: (Int) throws -> Void,
isCancelled: () -> Bool,
progress: (Double) -> Void) async throws -> URL {
try recorder.begin()
for n in 0..<frameCount {
if isCancelled() { break }
try renderFrame(n)
try recorder.appendFrame(index: n, pts: clipPresentationTime(frameIndex: n, fps: fps))
progress(Double(n + 1) / Double(frameCount))
}
return try await recorder.finish()
}