Browse Source

feat(network): resilient dual-host LAN/WAN failover, fast 3.5s connect timeout, and 4s keep-alive ping

main
Ali Alavi 10 hours ago
parent
commit
50d185e69c
  1. 33
      android/app/src/main/java/com/soniox/remotemic/MainActivity.kt
  2. 26
      android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt

33
android/app/src/main/java/com/soniox/remotemic/MainActivity.kt

@ -41,6 +41,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
@ -88,7 +89,7 @@ class MainActivity : AppCompatActivity() {
setupUI()
checkPermissions()
AppLogger.log("Main", "اپلیکیشن همگام‌سازی صوتی سانی‌اوکس راه‌اندازی شد (نسخه بهینه‌شده فوق‌سریع v5.5)")
AppLogger.log("Main", "اپلیکیشن همگام‌سازی صوتی سانی‌اوکس راه‌اندازی شد (نسخه بهینه‌شده فوق‌سریع v5.6)")
// Initialize Collaborative WebSocket Client
streamDictationClient = StreamDictationClient(
@ -376,7 +377,11 @@ class MainActivity : AppCompatActivity() {
private suspend fun sendDirectPaste(text: String, cursor: Int): Boolean {
return try {
val client = OkHttpClient()
val client = OkHttpClient.Builder()
.connectTimeout(2500, TimeUnit.MILLISECONDS)
.writeTimeout(3000, TimeUnit.MILLISECONDS)
.readTimeout(3000, TimeUnit.MILLISECONDS)
.build()
val json = JSONObject().apply {
put("text", text)
put("cursor_pos", cursor)
@ -384,13 +389,25 @@ class MainActivity : AppCompatActivity() {
}.toString()
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = json.toRequestBody(mediaType)
val req = Request.Builder()
.url("http://$gatewayHost/paste")
.post(body)
.build()
client.newCall(req).execute().use { resp ->
resp.isSuccessful
val hosts = listOf(gatewayHost, "116.16.16.19:8999", "2.180.16.250:8999").distinct()
var success = false
for (h in hosts) {
try {
val req = Request.Builder()
.url("http://$h/paste")
.post(body)
.build()
client.newCall(req).execute().use { resp ->
if (resp.isSuccessful) {
success = true
return@use
}
}
if (success) break
} catch (_: Exception) {}
}
success
} catch (e: Exception) {
AppLogger.log("Main", "خطای ارسال دستی: ${e.message}")
false

26
android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt

@ -44,11 +44,17 @@ class StreamDictationClient(
private val isRecording = AtomicBoolean(false)
private val mainHandler = Handler(Looper.getMainLooper())
private val candidateHosts: List<String> = listOf(
host,
if (host.contains("116.16.16.19")) "2.180.16.250:8999" else "116.16.16.19:8999"
).filter { it.isNotBlank() }.distinct()
private var currentHostIndex = 0
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(12, TimeUnit.SECONDS)
.connectTimeout(3500, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS) // Persistent WebSocket
.writeTimeout(8, TimeUnit.SECONDS)
.pingInterval(8, TimeUnit.SECONDS)
.writeTimeout(5000, TimeUnit.MILLISECONDS)
.pingInterval(4, TimeUnit.SECONDS) // 4s active heartbeat to keep NAT tables alive
.retryOnConnectionFailure(true)
.build()
@ -68,18 +74,20 @@ class StreamDictationClient(
if (isConnected.get() && activeWebSocket != null) return
if (isConnecting.getAndSet(true)) return
val cleanHost = host.removePrefix("http://").removePrefix("https://").removePrefix("ws://").removePrefix("wss://")
val targetHost = candidateHosts[currentHostIndex % candidateHosts.size]
val cleanHost = targetHost.removePrefix("http://").removePrefix("https://").removePrefix("ws://").removePrefix("wss://")
val wsUrl = "ws://$cleanHost/ws/stream"
AppLogger.log(tag, "اتصال به سوکت همگام‌سازی بلادرنگ: $wsUrl")
AppLogger.log(tag, "اتصال به سوکت همگام‌سازی بلادرنگ ($targetHost): $wsUrl")
val req = Request.Builder()
.url(wsUrl)
.header("User-Agent", "SonioxAndroidRemote/5.4")
.header("User-Agent", "SonioxAndroidRemote/5.6")
.build()
activeWebSocket = okHttpClient.newWebSocket(req, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
AppLogger.log(tag, "🟢 سوکت همگام‌سازی بلادرنگ متصل شد")
val connectedHost = candidateHosts[currentHostIndex % candidateHosts.size]
AppLogger.log(tag, "🟢 سوکت همگام‌سازی بلادرنگ متصل شد ($connectedHost)")
isConnected.set(true)
isConnecting.set(false)
retryAttempt = 0
@ -146,8 +154,10 @@ class StreamDictationClient(
activeWebSocket = null
mainHandler.post { onConnectionStateChanged(false) }
// Fast failover between LAN and WAN
currentHostIndex++
retryAttempt++
val delayMs = minOf(800L * (1L shl minOf(retryAttempt, 3)), 5000L)
val delayMs = if (retryAttempt <= 2) 350L else minOf(500L * (1L shl minOf(retryAttempt, 2)), 2000L)
mainHandler.postDelayed({ connectWebSocket() }, delayMs)
}
})

Loading…
Cancel
Save