|
|
|
@ -26,6 +26,7 @@ public final class SonioxLiveSession { |
|
|
|
|
|
|
|
public private(set) var hasNetworkFailure: Bool = false |
|
|
|
private var lastDataTime: Date = Date() |
|
|
|
private var heartbeatTimer: Timer? |
|
|
|
|
|
|
|
public var isReady: Bool { |
|
|
|
lock.lock() |
|
|
|
@ -33,6 +34,42 @@ public final class SonioxLiveSession { |
|
|
|
return isConnected && !isClosed && !isFinalizing |
|
|
|
} |
|
|
|
|
|
|
|
public func startKeepAliveHeartbeat() { |
|
|
|
DispatchQueue.main.async { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
self.heartbeatTimer?.invalidate() |
|
|
|
self.heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 6.0, repeats: true) { [weak self] _ in |
|
|
|
guard let self = self else { return } |
|
|
|
self.lock.lock() |
|
|
|
guard !self.isClosed, !self.isFinalizing, let task = self.webSocketTask else { |
|
|
|
self.lock.unlock() |
|
|
|
return |
|
|
|
} |
|
|
|
self.lock.unlock() |
|
|
|
|
|
|
|
task.sendPing { [weak self] error in |
|
|
|
guard let self = self else { return } |
|
|
|
if let error = error { |
|
|
|
print("SonioxLiveSession: Heartbeat ping failed (\(error)). Marking stale.") |
|
|
|
self.lock.lock() |
|
|
|
self.isConnected = false |
|
|
|
self.hasNetworkFailure = true |
|
|
|
self.isClosed = true |
|
|
|
self.lock.unlock() |
|
|
|
SonioxSessionPool.shared.prewarmNextSession() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public func stopKeepAliveHeartbeat() { |
|
|
|
DispatchQueue.main.async { [weak self] in |
|
|
|
self?.heartbeatTimer?.invalidate() |
|
|
|
self?.heartbeatTimer = nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public init(languageHints: [String] = ["fa", "en", "ar"], endpoint: String = SonioxLiveSession.primaryWsBase) { |
|
|
|
let hints = languageHints.map { "language_hints=\($0)" }.joined(separator: "&") |
|
|
|
let urlStr = "\(endpoint)?\(hints)&enable_speaker_diarization=false&enable_language_identification=true&enable_endpoint_detection=false&providers=soniox" |
|
|
|
@ -493,6 +530,7 @@ public final class SonioxSessionPool { |
|
|
|
|
|
|
|
private var prewarmedSession: SonioxLiveSession? |
|
|
|
private let lock = NSLock() |
|
|
|
private var isPrewarming = false |
|
|
|
|
|
|
|
private init() { |
|
|
|
prewarmNextSession() |
|
|
|
@ -506,15 +544,37 @@ public final class SonioxSessionPool { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
let hints = SonioxSettings.shared.language.hints |
|
|
|
let session = SonioxLiveSession(languageHints: hints) |
|
|
|
self.prewarmedSession = session |
|
|
|
if isPrewarming { return } |
|
|
|
isPrewarming = true |
|
|
|
|
|
|
|
prewarmedSession?.stopKeepAliveHeartbeat() |
|
|
|
prewarmedSession?.cancel() |
|
|
|
prewarmedSession = nil |
|
|
|
|
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
let hints = SonioxSettings.shared.language.hints |
|
|
|
let session = SonioxLiveSession(languageHints: hints) |
|
|
|
|
|
|
|
// Allow 400ms for connection & initial ping |
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
self.lock.lock() |
|
|
|
session.startKeepAliveHeartbeat() |
|
|
|
self.prewarmedSession = session |
|
|
|
self.isPrewarming = false |
|
|
|
self.lock.unlock() |
|
|
|
print("SonioxSessionPool: Fresh prewarmed session initialized & heartbeat started.") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public func reconnectAll() { |
|
|
|
lock.lock() |
|
|
|
prewarmedSession?.stopKeepAliveHeartbeat() |
|
|
|
prewarmedSession?.cancel() |
|
|
|
prewarmedSession = nil |
|
|
|
isPrewarming = false |
|
|
|
lock.unlock() |
|
|
|
prewarmNextSession() |
|
|
|
} |
|
|
|
@ -525,14 +585,18 @@ public final class SonioxSessionPool { |
|
|
|
prewarmedSession = nil |
|
|
|
lock.unlock() |
|
|
|
|
|
|
|
DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) { [weak self] in |
|
|
|
// Immediately start pre-warming the NEXT session in the background |
|
|
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in |
|
|
|
self?.prewarmNextSession() |
|
|
|
} |
|
|
|
|
|
|
|
if let session = session, session.isReady { |
|
|
|
session.stopKeepAliveHeartbeat() |
|
|
|
print("SonioxSessionPool: Acquired HOT prewarmed session (0ms connect delay!)") |
|
|
|
return session |
|
|
|
} |
|
|
|
|
|
|
|
print("SonioxSessionPool: Prewarmed session not ready yet, creating immediate on-demand session.") |
|
|
|
let hints = SonioxSettings.shared.language.hints |
|
|
|
return SonioxLiveSession(languageHints: hints) |
|
|
|
} |
|
|
|
|