feat(tts): Remove auto engine and fix ios STS
This commit is contained in:
@@ -23,7 +23,7 @@ class TextToSpeechService {
|
||||
final FlutterTts _tts = FlutterTts();
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
final ApiService? _api;
|
||||
TtsEngine _engine = TtsEngine.auto;
|
||||
TtsEngine _engine = TtsEngine.device;
|
||||
String? _preferredVoice;
|
||||
String? _serverPreferredVoice;
|
||||
double _speechRate = 0.5;
|
||||
@@ -127,11 +127,9 @@ class TextToSpeechService {
|
||||
final serverAvailable = _api != null;
|
||||
switch (_engine) {
|
||||
case TtsEngine.device:
|
||||
return _deviceEngineAvailable;
|
||||
return _deviceEngineAvailable || serverAvailable;
|
||||
case TtsEngine.server:
|
||||
return serverAvailable;
|
||||
case TtsEngine.auto:
|
||||
return _deviceEngineAvailable || serverAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +137,7 @@ class TextToSpeechService {
|
||||
if (_engine == TtsEngine.server) {
|
||||
return _api != null;
|
||||
}
|
||||
if (_engine == TtsEngine.device) {
|
||||
return false;
|
||||
}
|
||||
// Auto: prefer device when available, otherwise fall back to server
|
||||
// Device preference with graceful fallback to server if available.
|
||||
if (_deviceEngineAvailable) {
|
||||
return false;
|
||||
}
|
||||
@@ -191,7 +186,7 @@ class TextToSpeechService {
|
||||
double speechRate = 0.5,
|
||||
double pitch = 1.0,
|
||||
double volume = 1.0,
|
||||
TtsEngine engine = TtsEngine.auto,
|
||||
TtsEngine engine = TtsEngine.device,
|
||||
}) async {
|
||||
if (_initialized) {
|
||||
_engine = engine;
|
||||
|
||||
@@ -133,9 +133,8 @@ class VoiceCallService {
|
||||
final hasLocalStt = _voiceInput.hasLocalStt;
|
||||
final hasServerStt = _voiceInput.hasServerStt;
|
||||
final ready = switch (_voiceInput.preference) {
|
||||
SttPreference.deviceOnly => hasLocalStt,
|
||||
SttPreference.deviceOnly => hasLocalStt || hasServerStt,
|
||||
SttPreference.serverOnly => hasServerStt,
|
||||
SttPreference.auto => hasLocalStt || hasServerStt,
|
||||
};
|
||||
|
||||
if (!ready) {
|
||||
@@ -240,9 +239,8 @@ class VoiceCallService {
|
||||
final hasServerStt = _voiceInput.hasServerStt;
|
||||
final pref = _voiceInput.preference;
|
||||
final engineAvailable = switch (pref) {
|
||||
SttPreference.deviceOnly => hasLocalStt,
|
||||
SttPreference.deviceOnly => hasLocalStt || hasServerStt,
|
||||
SttPreference.serverOnly => hasServerStt,
|
||||
SttPreference.auto => hasLocalStt || hasServerStt,
|
||||
};
|
||||
|
||||
if (!engineAvailable) {
|
||||
|
||||
@@ -36,7 +36,7 @@ class VoiceInputService {
|
||||
bool _isInitialized = false;
|
||||
bool _isListening = false;
|
||||
bool _localSttAvailable = false;
|
||||
SttPreference _preference = SttPreference.auto;
|
||||
SttPreference _preference = SttPreference.deviceOnly;
|
||||
bool _usingServerStt = false;
|
||||
String? _selectedLocaleId;
|
||||
List<LocaleName> _locales = const [];
|
||||
@@ -63,7 +63,6 @@ class VoiceInputService {
|
||||
bool get isSupportedPlatform => Platform.isAndroid || Platform.isIOS;
|
||||
bool get hasServerStt => _api != null;
|
||||
SttPreference get preference => _preference;
|
||||
bool get allowsServerFallback => _preference != SttPreference.deviceOnly;
|
||||
bool get prefersServerOnly => _preference == SttPreference.serverOnly;
|
||||
bool get prefersDeviceOnly => _preference == SttPreference.deviceOnly;
|
||||
|
||||
@@ -101,15 +100,9 @@ class VoiceInputService {
|
||||
try {
|
||||
final sttGranted = await _speech.hasPermission();
|
||||
if (!sttGranted) {
|
||||
if (prefersDeviceOnly) {
|
||||
return false;
|
||||
}
|
||||
_localSttAvailable = false;
|
||||
}
|
||||
} catch (_) {
|
||||
if (prefersDeviceOnly) {
|
||||
return false;
|
||||
}
|
||||
_localSttAvailable = false;
|
||||
}
|
||||
}
|
||||
@@ -248,11 +241,6 @@ class VoiceInputService {
|
||||
? 'Speech recognition failed'
|
||||
: message,
|
||||
);
|
||||
if (hasServerStt && allowsServerFallback) {
|
||||
_textStreamController?.addError(exception);
|
||||
unawaited(_beginServerFallback());
|
||||
return;
|
||||
}
|
||||
_textStreamController?.addError(exception);
|
||||
unawaited(_stopListening());
|
||||
}
|
||||
@@ -265,6 +253,35 @@ class VoiceInputService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startLocalRecognition({
|
||||
required bool allowOnlineFallback,
|
||||
}) async {
|
||||
if (_selectedLocaleId != null) {
|
||||
await _speech.setLanguage(_selectedLocaleId!);
|
||||
}
|
||||
|
||||
Future<void> attempt(bool offline) => _speech.start(
|
||||
SttRecognitionOptions(punctuation: true, offline: offline),
|
||||
);
|
||||
|
||||
try {
|
||||
await attempt(true);
|
||||
} catch (error) {
|
||||
if (Platform.isIOS && allowOnlineFallback) {
|
||||
try {
|
||||
await attempt(false);
|
||||
return;
|
||||
} catch (secondary) {
|
||||
throw Exception(
|
||||
'On-device speech failed ($error); '
|
||||
'online fallback failed ($secondary).',
|
||||
);
|
||||
}
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Stream<String> startListening() {
|
||||
if (!_isInitialized) {
|
||||
throw Exception('Voice input not initialized');
|
||||
@@ -304,11 +321,10 @@ class VoiceInputService {
|
||||
final isStillAvailable = await _speech.isSupported();
|
||||
if (!isStillAvailable && _isListening) {
|
||||
_localSttAvailable = false;
|
||||
if (hasServerStt && allowsServerFallback) {
|
||||
unawaited(_beginServerFallback());
|
||||
} else {
|
||||
unawaited(_stopListening());
|
||||
}
|
||||
_textStreamController?.addError(
|
||||
Exception('On-device speech recognition unavailable'),
|
||||
);
|
||||
unawaited(_stopListening());
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore availability check errors
|
||||
@@ -338,19 +354,12 @@ class VoiceInputService {
|
||||
|
||||
Future(() async {
|
||||
try {
|
||||
if (_selectedLocaleId != null) {
|
||||
await _speech.setLanguage(_selectedLocaleId!);
|
||||
}
|
||||
await _speech.start(SttRecognitionOptions(punctuation: true));
|
||||
await _startLocalRecognition(allowOnlineFallback: !prefersDeviceOnly);
|
||||
} catch (error) {
|
||||
_localSttAvailable = false;
|
||||
if (!_isListening) return;
|
||||
if (hasServerStt && allowsServerFallback) {
|
||||
await _beginServerFallback();
|
||||
} else {
|
||||
_textStreamController?.addError(error);
|
||||
await _stopListening();
|
||||
}
|
||||
_textStreamController?.addError(error);
|
||||
await _stopListening();
|
||||
}
|
||||
});
|
||||
} else if (shouldUseServer) {
|
||||
@@ -457,39 +466,6 @@ class VoiceInputService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _beginServerFallback() async {
|
||||
if (!allowsServerFallback) {
|
||||
_textStreamController?.addError(
|
||||
Exception('Server speech-to-text disabled in preferences'),
|
||||
);
|
||||
await _stopListening();
|
||||
return;
|
||||
}
|
||||
await _stopLocalStt();
|
||||
if (!hasServerStt) {
|
||||
_textStreamController?.addError(
|
||||
Exception('Server speech-to-text unavailable'),
|
||||
);
|
||||
await _stopListening();
|
||||
return;
|
||||
}
|
||||
|
||||
_usingServerStt = true;
|
||||
_autoStopTimer?.cancel();
|
||||
_autoStopTimer = Timer(const Duration(seconds: 90), () {
|
||||
if (_isListening) {
|
||||
unawaited(_stopListening());
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await _startServerRecording();
|
||||
} catch (error) {
|
||||
_textStreamController?.addError(error);
|
||||
await _stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startServerRecording() async {
|
||||
await _setupVadStreams();
|
||||
final settings = _ref?.read(appSettingsProvider);
|
||||
@@ -823,13 +799,11 @@ Future<bool> voiceInputAvailable(Ref ref) async {
|
||||
if (!initialized) return false;
|
||||
switch (service.preference) {
|
||||
case SttPreference.deviceOnly:
|
||||
return service.hasLocalStt;
|
||||
case SttPreference.serverOnly:
|
||||
return service.hasServerStt;
|
||||
case SttPreference.auto:
|
||||
if (service.hasLocalStt) return true;
|
||||
if (!service.hasServerStt) return false;
|
||||
break;
|
||||
case SttPreference.serverOnly:
|
||||
return service.hasServerStt;
|
||||
}
|
||||
final hasPermission = await service.checkPermissions();
|
||||
if (!hasPermission) return false;
|
||||
|
||||
Reference in New Issue
Block a user