feat(voice-call): Improve socket connection and mic permission handling
This commit is contained in:
@@ -158,11 +158,15 @@ class VoiceCallService {
|
||||
throw Exception('Preferred speech recognition engine is unavailable');
|
||||
}
|
||||
|
||||
// Check microphone permissions
|
||||
final hasMicPermission = await _voiceInput.checkPermissions();
|
||||
// Check and request microphone permissions if needed
|
||||
var hasMicPermission = await _voiceInput.checkPermissions();
|
||||
if (!hasMicPermission) {
|
||||
_updateState(VoiceCallState.error);
|
||||
throw Exception('Microphone permission not granted');
|
||||
// Try to request permission
|
||||
hasMicPermission = await _voiceInput.requestMicrophonePermission();
|
||||
if (!hasMicPermission) {
|
||||
_updateState(VoiceCallState.error);
|
||||
throw Exception('Microphone permission not granted');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize TTS with current app settings (engine/voice/rate/pitch/volume)
|
||||
@@ -309,11 +313,14 @@ class VoiceCallService {
|
||||
// Enable wake lock to keep screen on and prevent audio interruption
|
||||
await WakelockPlus.enable();
|
||||
|
||||
// Ensure socket connection
|
||||
await _socketService.ensureConnected();
|
||||
// Ensure socket connection with extended timeout for app startup scenarios.
|
||||
// Default 2s is too short when app is launched from deep links/shortcuts.
|
||||
final connected = await _socketService.ensureConnected(
|
||||
timeout: const Duration(seconds: 10),
|
||||
);
|
||||
_sessionId = _socketService.sessionId;
|
||||
|
||||
if (_sessionId == null) {
|
||||
if (!connected || _sessionId == null) {
|
||||
throw Exception('Failed to establish socket connection');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:io' show File, Platform;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -343,6 +345,51 @@ class VoiceInputService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests microphone permission if not already granted.
|
||||
/// Returns true if permission is granted, false otherwise.
|
||||
Future<bool> requestMicrophonePermission() async {
|
||||
try {
|
||||
// First check if we already have permission
|
||||
var hasPermission = await _microphonePermissionProbe.hasPermission();
|
||||
if (hasPermission) return true;
|
||||
|
||||
// The record package's start() method will trigger the system permission
|
||||
// dialog if permission hasn't been granted yet. We start a brief recording
|
||||
// and immediately stop it to trigger the permission request.
|
||||
try {
|
||||
// Create a temporary file path for the recording probe.
|
||||
// An empty path only works on web; mobile platforms need a real path.
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempPath =
|
||||
'${tempDir.path}/mic_permission_probe_${DateTime.now().millisecondsSinceEpoch}.wav';
|
||||
|
||||
await _microphonePermissionProbe.start(
|
||||
const RecordConfig(encoder: AudioEncoder.wav),
|
||||
path: tempPath,
|
||||
);
|
||||
await _microphonePermissionProbe.stop();
|
||||
|
||||
// Clean up the temporary file
|
||||
try {
|
||||
final tempFile = File(tempPath);
|
||||
if (await tempFile.exists()) {
|
||||
await tempFile.delete();
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
} catch (_) {
|
||||
// Starting may fail if permission is denied, which is expected
|
||||
}
|
||||
|
||||
// Check again after the permission request attempt
|
||||
hasPermission = await _microphonePermissionProbe.hasPermission();
|
||||
return hasPermission;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startLocalRecognition({
|
||||
required bool allowOnlineFallback,
|
||||
}) async {
|
||||
|
||||
Reference in New Issue
Block a user