diff --git a/Sources/ElevenLabs/Conversation.swift b/Sources/ElevenLabs/Conversation.swift index 1091fa2..2f978fa 100644 --- a/Sources/ElevenLabs/Conversation.swift +++ b/Sources/ElevenLabs/Conversation.swift @@ -18,7 +18,7 @@ public final class Conversation: ObservableObject, RoomDelegate { @Published public private(set) var startupState: ConversationStartupState = .idle @Published public private(set) var startupMetrics: ConversationStartupMetrics? @Published public private(set) var messages: [Message] = [] - @Published public private(set) var agentState: AgentState = .listening + @Published public private(set) var agentState: ElevenLabs.AgentState = .listening @Published public private(set) var isMuted: Bool = true // Start as true, will be updated based on actual state /// Stream of client tool calls that need to be executed by the app @@ -51,6 +51,7 @@ public final class Conversation: ObservableObject, RoomDelegate { private var lastFeedbackSubmittedEventId: Int? private var previousSpeechActivityHandler: AudioManager.OnSpeechActivity? private var audioSpeechHandlerInstalled = false + private var shouldAbortRetries = false // Audio tracks for advanced use cases public var inputTrack: LocalAudioTrack? { @@ -299,6 +300,7 @@ public final class Conversation: ObservableObject, RoomDelegate { /// End and clean up. public func endConversation() async { + shouldAbortRetries = true guard state.isActive else { return } guard let connectionManager = resolvedConnectionManager() else { return } await connectionManager.disconnect() @@ -887,6 +889,7 @@ public final class Conversation: ObservableObject, RoomDelegate { ? [0] : options.startupConfiguration.initRetryDelays + self.shouldAbortRetries = false for (index, delay) in delays.enumerated() { let attemptNumber = index + 1 metrics.conversationInitAttempts = attemptNumber @@ -894,7 +897,13 @@ public final class Conversation: ObservableObject, RoomDelegate { if delay > 0 { print("[Retry] Attempt \(attemptNumber) delay: \(delay)s") - try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) + try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) + try Task.checkCancellation() + } + + if shouldAbortRetries { + print("[Retry] Aborting retry loop after sleep") + throw CancellationError() } let attemptStart = Date() diff --git a/Sources/ElevenLabs/Models/LiveKitNetworkConfiguration.swift b/Sources/ElevenLabs/Models/LiveKitNetworkConfiguration.swift index 11a57a9..81b8ce8 100644 --- a/Sources/ElevenLabs/Models/LiveKitNetworkConfiguration.swift +++ b/Sources/ElevenLabs/Models/LiveKitNetworkConfiguration.swift @@ -24,9 +24,13 @@ public struct LiveKitNetworkConfiguration: Sendable { /// Optional custom ICE servers to use instead of those supplied by the ElevenLabs backend. public var customIceServers: [IceServer] - public init(strategy: Strategy = .automatic, customIceServers: [IceServer] = []) { + /// Optional override for LiveKit's reconnectAttempts connection option + public var reconnectAttempts: Int? = nil + + public init(strategy: Strategy = .automatic, customIceServers: [IceServer] = [], reconnectAttempts: Int? = nil) { self.strategy = strategy self.customIceServers = customIceServers + self.reconnectAttempts = reconnectAttempts } /// Default configuration (automatic ICE candidate gathering, no custom ICE servers). @@ -46,7 +50,7 @@ extension LiveKitNetworkConfiguration { } var requiresCustomConnectOptions: Bool { - strategy != .automatic || !customIceServers.isEmpty + strategy != .automatic || !customIceServers.isEmpty || reconnectAttempts != nil } func makeConnectOptions() -> ConnectOptions? { @@ -62,6 +66,7 @@ extension LiveKitNetworkConfiguration { #endif return ConnectOptions( + reconnectAttempts: reconnectAttempts ?? ConnectOptions().reconnectAttempts, iceServers: customIceServers, iceTransportPolicy: policy )