diff --git a/Meetingnotes.xcodeproj/project.pbxproj b/Meetingnotes.xcodeproj/project.pbxproj index 6c200f6..3a9e611 100644 --- a/Meetingnotes.xcodeproj/project.pbxproj +++ b/Meetingnotes.xcodeproj/project.pbxproj @@ -276,7 +276,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 37; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = G9LVHZAJNX; ENABLE_HARDENED_RUNTIME = YES; @@ -290,7 +290,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.1.24; + MARKETING_VERSION = 1.1.25; ONLY_ACTIVE_ARCH = NO; OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI"; PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes; @@ -312,7 +312,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 37; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = G9LVHZAJNX; ENABLE_HARDENED_RUNTIME = YES; @@ -326,7 +326,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.1.24; + MARKETING_VERSION = 1.1.25; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI"; PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes; diff --git a/README.md b/README.md index 9911bf4..60b0e08 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Implemented: - Meeting search functionality - Abilty to edit system prompt - Select any compatible Coder model for transcription and note generation -- Automatic start and stop from MuteDeck through a compatible local API +- Automatic start and stop from MuteDeck through a compatible local API, with a 10-second reconnect grace period - Merge an automatically split continuation back into its previous meeting - Auto updates - Text formatting diff --git a/meetingnotes/Services/LocalAPIServer.swift b/meetingnotes/Services/LocalAPIServer.swift index a6e966f..f0fa97f 100644 --- a/meetingnotes/Services/LocalAPIServer.swift +++ b/meetingnotes/Services/LocalAPIServer.swift @@ -200,7 +200,9 @@ private final class LocalRecordingController { static let shared = LocalRecordingController() private let recordingManager = RecordingSessionManager.shared + private let stopGraceNanoseconds: UInt64 = 10_000_000_000 private var isStopping = false + private var pendingStopTask: Task? private init() {} @@ -237,6 +239,12 @@ private final class LocalRecordingController { } func startRecording() throws -> [String: Any] { + if let pendingStopTask { + pendingStopTask.cancel() + self.pendingStopTask = nil + isStopping = false + return statusPayload() + } if isStopping || recordingManager.isProcessing { throw LocalRecordingError.processing } @@ -258,14 +266,22 @@ private final class LocalRecordingController { return statusPayload() } isStopping = true - Task { [weak self] in + pendingStopTask = Task { [weak self] in + try? await Task.sleep(nanoseconds: self?.stopGraceNanoseconds ?? 0) + guard !Task.isCancelled else { return } await self?.finishRecording() } return statusPayload() } func cancelRecording() -> [String: Any] { - guard !isStopping else { return statusPayload() } + if let pendingStopTask { + pendingStopTask.cancel() + self.pendingStopTask = nil + isStopping = false + } else if isStopping { + return statusPayload() + } let meetingID = recordingManager.activeMeetingId recordingManager.cancelRecording() if let meetingID, @@ -277,6 +293,7 @@ private final class LocalRecordingController { } private func finishRecording() async { + pendingStopTask = nil let meetingID = recordingManager.activeMeetingId let chunks = await recordingManager.stopRecording() guard let meetingID,