Browse Source
feat: real-time 1s ping monitor to Soniox with dynamic orb color (green/yellow/orange/red) and millisecond HUD readout
main
feat: real-time 1s ping monitor to Soniox with dynamic orb color (green/yellow/orange/red) and millisecond HUD readout
main
3 changed files with 153 additions and 14 deletions
@ -0,0 +1,93 @@ |
|||
import Foundation |
|||
|
|||
public enum PingQuality { |
|||
case good(ms: Int) // < 450 ms -> Emerald Green |
|||
case medium(ms: Int) // 450 - 750 ms -> Warm Yellow/Lime |
|||
case high(ms: Int) // 750 - 1500 ms -> Amber Orange |
|||
case disconnected(ms: Int?) // > 1500 ms or Timeout/Error -> Crimson Red |
|||
|
|||
public var labelText: String { |
|||
switch self { |
|||
case .good(let ms): return "\(ms)ms" |
|||
case .medium(let ms): return "\(ms)ms" |
|||
case .high(let ms): return "\(ms)ms" |
|||
case .disconnected(let ms): |
|||
if let ms = ms { |
|||
return "\(ms)ms" |
|||
} else { |
|||
return "OFF" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public final class PingMonitor { |
|||
public static let shared = PingMonitor() |
|||
|
|||
private let targetURL = URL(string: "https://translate.compare.soniox.com/")! |
|||
private var session: URLSession! |
|||
private var timer: Timer? |
|||
private var isRunning = false |
|||
|
|||
public var onPingUpdate: ((PingQuality) -> Void)? |
|||
public private(set) var currentQuality: PingQuality = .medium(ms: 380) |
|||
|
|||
private init() { |
|||
let config = URLSessionConfiguration.default |
|||
config.timeoutIntervalForRequest = 2.5 |
|||
config.timeoutIntervalForResource = 2.5 |
|||
config.waitsForConnectivity = false |
|||
config.requestCachePolicy = .reloadIgnoringLocalCacheData |
|||
self.session = URLSession(configuration: config) |
|||
} |
|||
|
|||
public func start() { |
|||
guard !isRunning else { return } |
|||
isRunning = true |
|||
measurePing() |
|||
DispatchQueue.main.async { [weak self] in |
|||
self?.timer?.invalidate() |
|||
self?.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in |
|||
self?.measurePing() |
|||
} |
|||
} |
|||
} |
|||
|
|||
public func stop() { |
|||
isRunning = false |
|||
timer?.invalidate() |
|||
timer = nil |
|||
} |
|||
|
|||
private func measurePing() { |
|||
var req = URLRequest(url: targetURL) |
|||
req.httpMethod = "HEAD" |
|||
let t0 = Date() |
|||
|
|||
let task = session.dataTask(with: req) { [weak self] _, resp, _ in |
|||
let elapsed = Date().timeIntervalSince(t0) * 1000.0 |
|||
let ms = Int(elapsed) |
|||
|
|||
let quality: PingQuality |
|||
if let http = resp as? HTTPURLResponse, (200...399).contains(http.statusCode) { |
|||
if ms < 450 { |
|||
quality = .good(ms: ms) |
|||
} else if ms <= 750 { |
|||
quality = .medium(ms: ms) |
|||
} else if ms <= 1500 { |
|||
quality = .high(ms: ms) |
|||
} else { |
|||
quality = .disconnected(ms: ms) |
|||
} |
|||
} else { |
|||
quality = .disconnected(ms: nil) |
|||
} |
|||
|
|||
DispatchQueue.main.async { |
|||
self?.currentQuality = quality |
|||
self?.onPingUpdate?(quality) |
|||
} |
|||
} |
|||
task.resume() |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue