feat: add AI voice transcription fallback for domestic Android ROMs

This commit is contained in:
shen
2026-09-15 22:02:26 +08:00
parent 670260f8e2
commit 35b493e652
8 changed files with 683 additions and 90 deletions
+64 -16
View File
@@ -19,6 +19,9 @@ class VoiceService {
bool _speechReady = false;
bool _ttsInitialized = false;
void Function(String status)? _statusListener;
void Function(String error)? _errorListener;
Future<void> _initTts() async {
if (_ttsInitialized) return;
try {
@@ -65,6 +68,8 @@ class VoiceService {
} catch (_) {}
}
Future<bool> hasRecordPermission() => _recorder.hasPermission();
Future<bool> startRecording() async {
if (!await _recorder.hasPermission()) return false;
final directory = await getApplicationDocumentsDirectory();
@@ -125,27 +130,70 @@ class VoiceService {
await _player.dispose();
}
Future<bool> initializeSpeech() async {
_speechReady = await _stt.initialize();
return _speechReady;
Future<bool> initializeSpeech({
void Function(String status)? onStatus,
void Function(String error)? onError,
}) async {
_statusListener = onStatus;
_errorListener = onError;
try {
_speechReady = await _stt.initialize(
onError: (val) {
_errorListener?.call(val.errorMsg);
},
onStatus: (val) {
_statusListener?.call(val);
},
debugLogging: false,
);
return _speechReady;
} catch (_) {
_speechReady = false;
return false;
}
}
Future<bool> startListening(
void Function(String text, bool finalResult) onResult,
) async {
if (!_speechReady && !await initializeSpeech()) {
void Function(String text, bool finalResult) onResult, {
void Function(String status)? onStatus,
void Function(String error)? onError,
}) async {
_statusListener = onStatus;
_errorListener = onError;
try {
if (!_speechReady || !_stt.isAvailable) {
final ready = await initializeSpeech(onStatus: onStatus, onError: onError);
if (!ready) return false;
}
String? targetLocaleId = 'en_US';
try {
final locales = await _stt.locales();
if (locales.isNotEmpty) {
final enLocale = locales.firstWhere(
(l) => l.localeId.toLowerCase().startsWith('en'),
orElse: () => locales.first,
);
targetLocaleId = enLocale.localeId;
}
} catch (_) {}
await _stt.listen(
onResult: (result) =>
onResult(result.recognizedWords, result.finalResult),
listenOptions: SpeechListenOptions(
localeId: targetLocaleId,
listenFor: const Duration(seconds: 30),
pauseFor: const Duration(seconds: 4),
partialResults: true,
cancelOnError: false,
),
);
return _stt.isListening;
} catch (e) {
if (onError != null) onError(e.toString());
return false;
}
await _stt.listen(
onResult: (result) =>
onResult(result.recognizedWords, result.finalResult),
listenOptions: SpeechListenOptions(
localeId: 'en_US',
listenFor: const Duration(seconds: 30),
pauseFor: const Duration(seconds: 4),
),
);
return true;
}
Future<void> stopListening() => _stt.stop();