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.
This commit is contained in:
cogwheel0
2025-10-09 16:18:14 +05:30
parent 43c7e5200b
commit a9030473b0
3 changed files with 44 additions and 7 deletions

View File

@@ -35,7 +35,7 @@ class BackgroundStreamingService : Service() {
const val NOTIFICATION_ID = 1001 const val NOTIFICATION_ID = 1001
const val ACTION_START = "START_STREAMING" const val ACTION_START = "START_STREAMING"
const val ACTION_STOP = "STOP_STREAMING" const val ACTION_STOP = "STOP_STREAMING"
private const val EXTRA_REQUIRES_MICROPHONE = "requiresMicrophone" const val EXTRA_REQUIRES_MICROPHONE = "requiresMicrophone"
} }
override fun onCreate() { override fun onCreate() {
@@ -197,6 +197,7 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
private lateinit var sharedPrefs: SharedPreferences private lateinit var sharedPrefs: SharedPreferences
private val activeStreams = mutableSetOf<String>() private val activeStreams = mutableSetOf<String>()
private val streamsRequiringMic = mutableSetOf<String>()
private var backgroundJob: Job? = null private var backgroundJob: Job? = null
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
@@ -219,8 +220,9 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
when (call.method) { when (call.method) {
"startBackgroundExecution" -> { "startBackgroundExecution" -> {
val streamIds = call.argument<List<String>>("streamIds") val streamIds = call.argument<List<String>>("streamIds")
val requiresMic = call.argument<Boolean>("requiresMicrophone") ?: false
if (streamIds != null) { if (streamIds != null) {
startBackgroundExecution(streamIds) startBackgroundExecution(streamIds, requiresMic)
result.success(null) result.success(null)
} else { } else {
result.error("INVALID_ARGS", "Stream IDs required", null) result.error("INVALID_ARGS", "Stream IDs required", null)
@@ -263,8 +265,11 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
} }
} }
private fun startBackgroundExecution(streamIds: List<String>) { private fun startBackgroundExecution(streamIds: List<String>, requiresMic: Boolean) {
activeStreams.addAll(streamIds) activeStreams.addAll(streamIds)
if (requiresMic) {
streamsRequiringMic.addAll(streamIds)
}
if (activeStreams.isNotEmpty()) { if (activeStreams.isNotEmpty()) {
startForegroundService() startForegroundService()
@@ -274,6 +279,7 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
private fun stopBackgroundExecution(streamIds: List<String>) { private fun stopBackgroundExecution(streamIds: List<String>) {
activeStreams.removeAll(streamIds.toSet()) activeStreams.removeAll(streamIds.toSet())
streamsRequiringMic.removeAll(streamIds.toSet())
if (activeStreams.isEmpty()) { if (activeStreams.isEmpty()) {
stopForegroundService() stopForegroundService()
@@ -285,6 +291,10 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
try { try {
val serviceIntent = Intent(context, BackgroundStreamingService::class.java) val serviceIntent = Intent(context, BackgroundStreamingService::class.java)
serviceIntent.putExtra("streamCount", activeStreams.size) serviceIntent.putExtra("streamCount", activeStreams.size)
serviceIntent.putExtra(
BackgroundStreamingService.EXTRA_REQUIRES_MICROPHONE,
streamsRequiringMic.isNotEmpty(),
)
serviceIntent.action = BackgroundStreamingService.ACTION_START serviceIntent.action = BackgroundStreamingService.ACTION_START
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 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}") println("BackgroundStreamingHandler: Failed to start foreground service: ${e.message}")
// Clear active streams as we couldn't start the service // Clear active streams as we couldn't start the service
activeStreams.clear() activeStreams.clear()
streamsRequiringMic.clear()
} }
} }
@@ -346,6 +357,10 @@ class BackgroundStreamingHandler(private val activity: MainActivity) : MethodCal
val serviceIntent = Intent(context, BackgroundStreamingService::class.java) val serviceIntent = Intent(context, BackgroundStreamingService::class.java)
serviceIntent.action = "KEEP_ALIVE" serviceIntent.action = "KEEP_ALIVE"
serviceIntent.putExtra("streamCount", activeStreams.size) serviceIntent.putExtra("streamCount", activeStreams.size)
serviceIntent.putExtra(
BackgroundStreamingService.EXTRA_REQUIRES_MICROPHONE,
streamsRequiringMic.isNotEmpty(),
)
context.startService(serviceIntent) context.startService(serviceIntent)
} }

View File

@@ -61,7 +61,10 @@ class BackgroundStreamingHandler {
} }
/// Start background execution for given stream IDs /// Start background execution for given stream IDs
Future<void> startBackgroundExecution(List<String> streamIds) async { Future<void> startBackgroundExecution(
List<String> streamIds, {
bool requiresMicrophone = false,
}) async {
if (!Platform.isIOS && !Platform.isAndroid) return; if (!Platform.isIOS && !Platform.isAndroid) return;
_activeStreamIds.addAll(streamIds); _activeStreamIds.addAll(streamIds);
@@ -69,6 +72,7 @@ class BackgroundStreamingHandler {
try { try {
await _channel.invokeMethod('startBackgroundExecution', { await _channel.invokeMethod('startBackgroundExecution', {
'streamIds': streamIds, 'streamIds': streamIds,
'requiresMicrophone': requiresMicrophone,
}); });
DebugLogger.stream( DebugLogger.stream(

View File

@@ -4,6 +4,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import '../../../core/providers/app_providers.dart'; import '../../../core/providers/app_providers.dart';
import '../../../core/services/background_streaming_handler.dart';
import '../../../core/services/socket_service.dart'; import '../../../core/services/socket_service.dart';
import '../../../core/utils/markdown_to_text.dart'; import '../../../core/utils/markdown_to_text.dart';
import '../providers/chat_providers.dart'; import '../providers/chat_providers.dart';
@@ -24,6 +25,8 @@ enum VoiceCallState {
} }
class VoiceCallService { class VoiceCallService {
static const String _voiceCallStreamId = 'voice-call';
final VoiceInputService _voiceInput; final VoiceInputService _voiceInput;
final TextToSpeechService _tts; final TextToSpeechService _tts;
final SocketService _socketService; final SocketService _socketService;
@@ -130,6 +133,10 @@ class VoiceCallService {
throw Exception('Failed to establish socket connection'); throw Exception('Failed to establish socket connection');
} }
await BackgroundStreamingHandler.instance.startBackgroundExecution(const [
_voiceCallStreamId,
], requiresMicrophone: true);
// Set up socket event listener for assistant responses // Set up socket event listener for assistant responses
_socketSubscription = _socketService.addChatEventHandler( _socketSubscription = _socketService.addChatEventHandler(
conversationId: conversationId, conversationId: conversationId,
@@ -144,6 +151,9 @@ class VoiceCallService {
_updateState(VoiceCallState.error); _updateState(VoiceCallState.error);
await WakelockPlus.disable(); await WakelockPlus.disable();
await _notificationService.cancelNotification(); await _notificationService.cancelNotification();
await BackgroundStreamingHandler.instance.stopBackgroundExecution(const [
_voiceCallStreamId,
]);
rethrow; rethrow;
} }
} }
@@ -331,6 +341,10 @@ class VoiceCallService {
await _voiceInput.stopListening(); await _voiceInput.stopListening();
await _tts.stop(); await _tts.stop();
await BackgroundStreamingHandler.instance.stopBackgroundExecution(const [
_voiceCallStreamId,
]);
// Cancel notification // Cancel notification
await _notificationService.cancelNotification(); await _notificationService.cancelNotification();
@@ -435,6 +449,10 @@ class VoiceCallService {
// Ensure wake lock is disabled on dispose // Ensure wake lock is disabled on dispose
await WakelockPlus.disable(); await WakelockPlus.disable();
await BackgroundStreamingHandler.instance.stopBackgroundExecution(const [
_voiceCallStreamId,
]);
await _stateController.close(); await _stateController.close();
await _transcriptController.close(); await _transcriptController.close();
await _responseController.close(); await _responseController.close();