From a9030473b0096e042d168d9f08168ac4c854a0c1 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:18:14 +0530 Subject: [PATCH] feat: enhance background streaming handler with microphone support - Updated BackgroundStreamingHandler to include microphone permission handling for background execution. - Modified startBackgroundExecution method to accept a requiresMicrophone parameter, allowing dynamic management of streams requiring microphone access. - Adjusted service intent to pass microphone requirement status, improving service behavior based on app state. - Enhanced VoiceCallService to utilize the new microphone support during voice call streaming, ensuring proper resource management. --- .../conduit/BackgroundStreamingHandler.kt | 27 ++++++++++++++----- .../background_streaming_handler.dart | 6 ++++- .../chat/services/voice_call_service.dart | 18 +++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/BackgroundStreamingHandler.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/BackgroundStreamingHandler.kt index b0fefb0..e7833cc 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/BackgroundStreamingHandler.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/BackgroundStreamingHandler.kt @@ -35,7 +35,7 @@ class BackgroundStreamingService : Service() { const val NOTIFICATION_ID = 1001 const val ACTION_START = "START_STREAMING" const val ACTION_STOP = "STOP_STREAMING" - private const val EXTRA_REQUIRES_MICROPHONE = "requiresMicrophone" + const val EXTRA_REQUIRES_MICROPHONE = "requiresMicrophone" } override fun onCreate() { @@ -195,8 +195,9 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal private lateinit var channel: MethodChannel private lateinit var context: Context private lateinit var sharedPrefs: SharedPreferences - + private val activeStreams = mutableSetOf() + private val streamsRequiringMic = mutableSetOf() private var backgroundJob: Job? = null private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) @@ -219,8 +220,9 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal when (call.method) { "startBackgroundExecution" -> { val streamIds = call.argument>("streamIds") + val requiresMic = call.argument("requiresMicrophone") ?: false if (streamIds != null) { - startBackgroundExecution(streamIds) + startBackgroundExecution(streamIds, requiresMic) result.success(null) } else { result.error("INVALID_ARGS", "Stream IDs required", null) @@ -263,9 +265,12 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal } } - private fun startBackgroundExecution(streamIds: List) { + private fun startBackgroundExecution(streamIds: List, requiresMic: Boolean) { activeStreams.addAll(streamIds) - + if (requiresMic) { + streamsRequiringMic.addAll(streamIds) + } + if (activeStreams.isNotEmpty()) { startForegroundService() startBackgroundMonitoring() @@ -274,7 +279,8 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal private fun stopBackgroundExecution(streamIds: List) { activeStreams.removeAll(streamIds.toSet()) - + streamsRequiringMic.removeAll(streamIds.toSet()) + if (activeStreams.isEmpty()) { stopForegroundService() stopBackgroundMonitoring() @@ -285,6 +291,10 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal try { val serviceIntent = Intent(context, BackgroundStreamingService::class.java) serviceIntent.putExtra("streamCount", activeStreams.size) + serviceIntent.putExtra( + BackgroundStreamingService.EXTRA_REQUIRES_MICROPHONE, + streamsRequiringMic.isNotEmpty(), + ) serviceIntent.action = BackgroundStreamingService.ACTION_START if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -296,6 +306,7 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal println("BackgroundStreamingHandler: Failed to start foreground service: ${e.message}") // Clear active streams as we couldn't start the service activeStreams.clear() + streamsRequiringMic.clear() } } @@ -346,6 +357,10 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal val serviceIntent = Intent(context, BackgroundStreamingService::class.java) serviceIntent.action = "KEEP_ALIVE" serviceIntent.putExtra("streamCount", activeStreams.size) + serviceIntent.putExtra( + BackgroundStreamingService.EXTRA_REQUIRES_MICROPHONE, + streamsRequiringMic.isNotEmpty(), + ) context.startService(serviceIntent) } diff --git a/lib/core/services/background_streaming_handler.dart b/lib/core/services/background_streaming_handler.dart index d271254..278004a 100644 --- a/lib/core/services/background_streaming_handler.dart +++ b/lib/core/services/background_streaming_handler.dart @@ -61,7 +61,10 @@ class BackgroundStreamingHandler { } /// Start background execution for given stream IDs - Future startBackgroundExecution(List streamIds) async { + Future startBackgroundExecution( + List streamIds, { + bool requiresMicrophone = false, + }) async { if (!Platform.isIOS && !Platform.isAndroid) return; _activeStreamIds.addAll(streamIds); @@ -69,6 +72,7 @@ class BackgroundStreamingHandler { try { await _channel.invokeMethod('startBackgroundExecution', { 'streamIds': streamIds, + 'requiresMicrophone': requiresMicrophone, }); DebugLogger.stream( diff --git a/lib/features/chat/services/voice_call_service.dart b/lib/features/chat/services/voice_call_service.dart index b9a3ae6..3f97944 100644 --- a/lib/features/chat/services/voice_call_service.dart +++ b/lib/features/chat/services/voice_call_service.dart @@ -4,6 +4,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import '../../../core/providers/app_providers.dart'; +import '../../../core/services/background_streaming_handler.dart'; import '../../../core/services/socket_service.dart'; import '../../../core/utils/markdown_to_text.dart'; import '../providers/chat_providers.dart'; @@ -24,6 +25,8 @@ enum VoiceCallState { } class VoiceCallService { + static const String _voiceCallStreamId = 'voice-call'; + final VoiceInputService _voiceInput; final TextToSpeechService _tts; final SocketService _socketService; @@ -130,6 +133,10 @@ class VoiceCallService { throw Exception('Failed to establish socket connection'); } + await BackgroundStreamingHandler.instance.startBackgroundExecution(const [ + _voiceCallStreamId, + ], requiresMicrophone: true); + // Set up socket event listener for assistant responses _socketSubscription = _socketService.addChatEventHandler( conversationId: conversationId, @@ -144,6 +151,9 @@ class VoiceCallService { _updateState(VoiceCallState.error); await WakelockPlus.disable(); await _notificationService.cancelNotification(); + await BackgroundStreamingHandler.instance.stopBackgroundExecution(const [ + _voiceCallStreamId, + ]); rethrow; } } @@ -331,6 +341,10 @@ class VoiceCallService { await _voiceInput.stopListening(); await _tts.stop(); + await BackgroundStreamingHandler.instance.stopBackgroundExecution(const [ + _voiceCallStreamId, + ]); + // Cancel notification await _notificationService.cancelNotification(); @@ -435,6 +449,10 @@ class VoiceCallService { // Ensure wake lock is disabled on dispose await WakelockPlus.disable(); + await BackgroundStreamingHandler.instance.stopBackgroundExecution(const [ + _voiceCallStreamId, + ]); + await _stateController.close(); await _transcriptController.close(); await _responseController.close();