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] 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/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..