diff --git a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt index 6017083..8bbdcb4 100644 --- a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt +++ b/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 diff --git a/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt b/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt index 35f1235..6c9c80b 100644 --- a/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt +++ b/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 = 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) } })