fix: add ping timers to prevent websocket disconnect (#35)
This commit is contained in:
@@ -30,6 +30,9 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
// Add current interim transcripts per source
|
// Add current interim transcripts per source
|
||||||
private var currentInterim: [AudioSource: String] = [.mic: "", .system: ""]
|
private var currentInterim: [AudioSource: String] = [.mic: "", .system: ""]
|
||||||
|
|
||||||
|
// Add ping timers to keep WebSocket connections alive
|
||||||
|
private var pingTimers: [AudioSource: Timer] = [:]
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
NotificationCenter.default.addObserver(forName: .AVAudioEngineConfigurationChange,
|
NotificationCenter.default.addObserver(forName: .AVAudioEngineConfigurationChange,
|
||||||
@@ -87,6 +90,10 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
systemSocketTask?.cancel(with: .normalClosure, reason: nil)
|
systemSocketTask?.cancel(with: .normalClosure, reason: nil)
|
||||||
systemSocketTask = nil
|
systemSocketTask = nil
|
||||||
|
|
||||||
|
// Invalidate ping timers
|
||||||
|
pingTimers.values.forEach { $0.invalidate() }
|
||||||
|
pingTimers.removeAll()
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
// (isRecording already cleared in stopRecording)
|
// (isRecording already cleared in stopRecording)
|
||||||
|
|
||||||
@@ -268,6 +275,10 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
systemSocketTask?.cancel(with: .normalClosure, reason: nil)
|
systemSocketTask?.cancel(with: .normalClosure, reason: nil)
|
||||||
systemSocketTask = nil
|
systemSocketTask = nil
|
||||||
|
|
||||||
|
// Invalidate ping timers
|
||||||
|
pingTimers.values.forEach { $0.invalidate() }
|
||||||
|
pingTimers.removeAll()
|
||||||
|
|
||||||
print("Recording stopped")
|
print("Recording stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,6 +332,22 @@ class AudioManager: NSObject, ObservableObject {
|
|||||||
// Add connection monitoring
|
// Add connection monitoring
|
||||||
task.resume()
|
task.resume()
|
||||||
|
|
||||||
|
// Set up ping timer to keep connection alive
|
||||||
|
pingTimers[source]?.invalidate()
|
||||||
|
let pingTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let task = source == .mic ? self.micSocketTask : self.systemSocketTask
|
||||||
|
guard let socket = task, socket.state == .running else { return }
|
||||||
|
socket.sendPing { error in
|
||||||
|
if let error = error {
|
||||||
|
print("❌ Ping failed for \(source): \(error)")
|
||||||
|
} else {
|
||||||
|
print("🏓 Ping sent for \(source)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pingTimers[source] = pingTimer
|
||||||
|
|
||||||
let thisSession = sessionID
|
let thisSession = sessionID
|
||||||
// Monitor connection state (ignore if session changed or recording stopped)
|
// Monitor connection state (ignore if session changed or recording stopped)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self, weak task] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self, weak task] in
|
||||||
|
|||||||
@@ -98,6 +98,16 @@ class ErrorHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle POSIX socket errors (e.g., "Socket is not connected")
|
||||||
|
if let nsError = error as NSError?, nsError.domain == NSPOSIXErrorDomain {
|
||||||
|
switch nsError.code {
|
||||||
|
case 57: // ENOTCONN - Socket is not connected
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WebSocket close codes
|
// WebSocket close codes
|
||||||
if let closeCode = (error as NSError?)?.userInfo["closeCode"] as? Int {
|
if let closeCode = (error as NSError?)?.userInfo["closeCode"] as? Int {
|
||||||
return closeCode < 4000 // Only retry for non-API errors
|
return closeCode < 4000 // Only retry for non-API errors
|
||||||
|
|||||||
Reference in New Issue
Block a user