وویس تایپینگ سانی اوکس مک
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

116 lines
3.9 KiB

import Foundation
import AppKit
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
private var isMeasuring = 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)
setupSleepWakeObservers()
}
private func setupSleepWakeObservers() {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: .main) { [weak self] _ in
print("PingMonitor: Mac entering sleep. Pausing ping measurements.")
self?.stop()
}
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: .main) { [weak self] _ in
print("PingMonitor: Mac woke from sleep. Resuming ping in 2.5s after network settles...")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
self?.start()
}
}
}
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
isMeasuring = false
timer?.invalidate()
timer = nil
}
private func measurePing() {
guard isRunning, !isMeasuring else { return }
isMeasuring = true
var req = URLRequest(url: targetURL)
req.httpMethod = "HEAD"
let t0 = Date()
let task = session.dataTask(with: req) { [weak self] _, resp, _ in
defer { self?.isMeasuring = false }
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()
}
}