feat: implement service failure handling in background streaming

- Added a method to send failure notifications to Flutter when the background service fails to enter the foreground.
- Implemented a broadcast receiver to handle service failure notifications and notify Flutter about the failure.
- Enhanced the persistent streaming service to attempt recovery for failed streams.
- Introduced heartbeat monitoring for SSE streams to detect stale connections and trigger recovery actions.
This commit is contained in:
cogwheel0
2025-10-28 13:59:17 +05:30
parent 81eb38dc52
commit 7fb199b2e4
7 changed files with 265 additions and 25 deletions

View File

@@ -102,9 +102,19 @@ class BackgroundStreamingService : Service() {
} catch (e: Exception) {
// Catch all exceptions including ForegroundServiceStartNotAllowedException
println("BackgroundStreamingService: Failed to enter foreground: ${e.javaClass.simpleName}: ${e.message}")
// Notify Flutter about the failure
sendFailureNotification(e)
false
}
}
private fun sendFailureNotification(e: Exception) {
// Send broadcast intent to notify MainActivity
val intent = Intent("app.cogwheel.conduit.FOREGROUND_SERVICE_FAILED")
intent.putExtra("error", e.message ?: "Unknown error")
intent.putExtra("errorType", e.javaClass.simpleName)
sendBroadcast(intent)
}
private fun updateForegroundType(notification: Notification, type: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return
@@ -248,6 +258,7 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
private val streamsRequiringMic = mutableSetOf<String>()
private var backgroundJob: Job? = null
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
private var serviceFailureReceiver: android.content.BroadcastReceiver? = null
companion object {
private const val CHANNEL_NAME = "conduit/background_streaming"
@@ -262,6 +273,38 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
createNotificationChannel()
setupServiceFailureReceiver()
}
private fun setupServiceFailureReceiver() {
serviceFailureReceiver = object : android.content.BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == "app.cogwheel.conduit.FOREGROUND_SERVICE_FAILED") {
val error = intent.getStringExtra("error") ?: "Unknown error"
val errorType = intent.getStringExtra("errorType") ?: "Exception"
println("BackgroundStreamingHandler: Service failure received: $errorType - $error")
// Notify Flutter about the service failure
channel.invokeMethod("serviceFailed", mapOf(
"error" to error,
"errorType" to errorType,
"streamIds" to activeStreams.toList()
))
// Clear active streams since service failed
activeStreams.clear()
streamsRequiringMic.clear()
}
}
}
val filter = android.content.IntentFilter("app.cogwheel.conduit.FOREGROUND_SERVICE_FAILED")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(serviceFailureReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
} else {
context.registerReceiver(serviceFailureReceiver, filter)
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
@@ -514,5 +557,15 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
scope.cancel()
stopBackgroundMonitoring()
stopForegroundService()
// Unregister broadcast receiver
try {
serviceFailureReceiver?.let {
context.unregisterReceiver(it)
}
} catch (e: Exception) {
println("BackgroundStreamingHandler: Error unregistering receiver: ${e.message}")
}
serviceFailureReceiver = null
}
}