From 0edbee61ee00c6c3515e92a9dae5bce6cbdaf073 Mon Sep 17 00:00:00 2001 From: Julien Romagnoli Date: Thu, 5 Feb 2026 10:37:17 +0100 Subject: [PATCH 1/3] fixing issue with restarting record --- TimeLapze/Camera.swift | 56 +++++++++++++++++++++++++++++++++++++++--- TimeLapze/Screen.swift | 49 +++++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/TimeLapze/Camera.swift b/TimeLapze/Camera.swift index 3f4b21c..315444c 100644 --- a/TimeLapze/Camera.swift +++ b/TimeLapze/Camera.swift @@ -22,6 +22,9 @@ class Camera: NSObject, Recordable { var lastAppendedFrame: CMTime = .zero var tmpFrameBuffer: CMSampleBuffer? + // Flag to prevent starting new recording while previous is finalizing + private var isFinalizingRecording = false + override var description: String { if inputDevice.manufacturer.isEmpty { return "\(self.inputDevice.localizedName)" @@ -93,27 +96,61 @@ class Camera: NSObject, Recordable { func startRecording() { guard self.enabled else { return } guard self.state != .recording else { return } + guard !isFinalizingRecording else { + logger.warning("Cannot start recording while previous recording is being finalized") + return + } logger.log("\(self.description) Recording") + // Reset state for new recording + resetRecordingState() + self.state = .recording setup(path: getFilename()) } + + /// Resets all state variables for a new recording session + private func resetRecordingState() { + // Clean up old objects + if let oldRecordVideo = recordVideo, oldRecordVideo.isRecording() { + oldRecordVideo.stopSession() + } + recordVideo = nil + writer = nil + input = nil + + // Reset time synchronization + offset = CMTime(seconds: 0.0, preferredTimescale: 60) + frameCount = 0 + frameChanged = true + lastAppendedFrame = .zero + tmpFrameBuffer = nil + } func saveRecording() { guard self.enabled else { return } self.state = .stopped + self.isFinalizingRecording = true logger.log("Camera - saved recording") if let recorder = recordVideo, recorder.isRecording() { recorder.stopSession() - logger.error("Stopped running") + logger.log("Stopped capture session") } guard let input = input, let writer = writer else { logger.log("Either the input or the writer is null") + self.isFinalizingRecording = false + return + } + + // Check if writer is in a valid state to finish + guard writer.status == .writing else { + logger.error("Writer is not in writing state, status: \(writer.status.rawValue)") + self.isFinalizingRecording = false return } @@ -123,8 +160,13 @@ class Camera: NSObject, Recordable { sleep(1) // sleeping for a second } - input.markAsFinished() // this is good + input.markAsFinished() writer.finishWriting { [self] in + defer { + // Always reset the flag when finalization is complete + self.isFinalizingRecording = false + } + if writer.status == .completed { // Asset writing completed successfully if UserDefaults.standard.bool(forKey: "showAfterSave") @@ -149,8 +191,16 @@ class Camera: NSObject, Recordable { // MARK: Streaming func handleVideo(buffer: CMSampleBuffer) { + // Ignore frames during finalization or when not recording + guard state == .recording, !isFinalizingRecording else { + return + } + guard let input = self.input, let writer = self.writer else { - logger.error("Not video writer present") + // Only log if we're actually supposed to be recording + if state == .recording { + logger.error("Not video writer present") + } return } diff --git a/TimeLapze/Screen.swift b/TimeLapze/Screen.swift index d33aa11..4ad6283 100644 --- a/TimeLapze/Screen.swift +++ b/TimeLapze/Screen.swift @@ -33,6 +33,9 @@ class Screen: NSObject, SCStreamOutput, Recordable { var height: Int? var width: Int? + + // Flag to prevent starting new recording while previous is finalizing + private var isFinalizingRecording = false override var description: String { if height == nil || width == nil { @@ -54,13 +57,36 @@ class Screen: NSObject, SCStreamOutput, Recordable { func startRecording(excluding: [SCRunningApplication], showCursor: Bool) { guard self.enabled else { return } guard self.state != .recording else { return } + guard !isFinalizingRecording else { + logger.warning("Cannot start recording while previous recording is being finalized") + return + } self.showCursor = showCursor + + // Reset state for new recording + resetRecordingState() self.state = .recording setup(path: getFilename(), excluding: excluding) } + + /// Resets all state variables for a new recording session + private func resetRecordingState() { + // Clean up old stream + stream = nil + writer = nil + input = nil + + // Reset time synchronization + offset = CMTime(seconds: 0.0, preferredTimescale: 60) + frameCount = 0 + frameChanged = true + lastAppendedFrame = .zero + tmpFrameBuffer = nil + lastSavedFrame = nil + } func pauseRecording() { self.state = .paused @@ -77,12 +103,20 @@ class Screen: NSObject, SCStreamOutput, Recordable { guard let writer = writer, let input = input else { return } self.state = .stopped + self.isFinalizingRecording = true logger.log("Screen -- saved recording") if let stream = stream { stream.stopCapture() } + + // Check if writer is in a valid state to finish + guard writer.status == .writing else { + logger.error("Writer is not in writing state, status: \(writer.status.rawValue)") + self.isFinalizingRecording = false + return + } while !input.isReadyForMoreMediaData { logger.log("Not able to mark the stream as finished") @@ -91,6 +125,11 @@ class Screen: NSObject, SCStreamOutput, Recordable { input.markAsFinished() writer.finishWriting { [self] in + defer { + // Always reset the flag when finalization is complete + self.isFinalizingRecording = false + } + if writer.status == .completed { // Asset writing completed successfully @@ -255,7 +294,8 @@ class Screen: NSObject, SCStreamOutput, Recordable { /// Saves each `CMSampleBuffer` from the screen func stream(_ stream: SCStream, didOutputSampleBuffer: CMSampleBuffer, of: SCStreamOutputType) { - guard self.state == .recording else { return } + // Ignore frames during finalization or when not recording + guard self.state == .recording, !isFinalizingRecording else { return } switch of { case .screen: @@ -269,8 +309,11 @@ class Screen: NSObject, SCStreamOutput, Recordable { /// Receives a list of `CMSampleBuffers` and uses `appendBuffer` to save them func handleVideo(buffer: CMSampleBuffer) { - guard self.input != nil else { // both - logger.error("No AVAssetWriter with the name `input` is present") + guard self.input != nil else { + // Only log if we're actually supposed to be recording + if state == .recording { + logger.error("No AVAssetWriter with the name `input` is present") + } return } From 2416a769b294fb018d4c0903dc156e991e07ec67 Mon Sep 17 00:00:00 2001 From: William Kaiser Date: Thu, 19 Feb 2026 16:06:36 -0500 Subject: [PATCH 2/3] Add numeric input for playback speed control --- TimeLapze/PreferencesView.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/TimeLapze/PreferencesView.swift b/TimeLapze/PreferencesView.swift index 4fa7e18..15f73f5 100644 --- a/TimeLapze/PreferencesView.swift +++ b/TimeLapze/PreferencesView.swift @@ -77,10 +77,22 @@ struct PreferencesView: View { ) HStack { - Text("\(String(format: "%.1f", preferencesViewModel.timeMultiple))x faster") - Slider(value: $preferencesViewModel.timeMultiple, in: .init(uncheckedBounds: (1.0, 240.0))) + TextField( + "", + value: Binding( + get: { preferencesViewModel.timeMultiple }, + set: { preferencesViewModel.timeMultiple = min(max($0, 1.0), 240.0) } + ), + format: .number.precision(.fractionLength(1)) + ) + .textFieldStyle(.roundedBorder) + .frame(width: 55) + .multilineTextAlignment(.trailing) + Text("x faster") } + Slider(value: $preferencesViewModel.timeMultiple, in: 1.0...240.0) + if #available(macOS 14.0, *) { Picker("Output FPS", selection: $preferencesViewModel.fpsDropdown) { ForEach(0.. Date: Mon, 2 Mar 2026 13:58:06 -0500 Subject: [PATCH 3/3] adding pre-commit --- .pre-commit-config.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3be5e3d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: + - repo: local + hooks: + - id: swift-format + name: swift format + entry: swift format --in-place --configuration .swift-format + language: system + types: [swift] + - id: swiftlint + name: swiftlint + entry: swiftlint lint --strict + language: system + types: [swift]