added alternative tts package for windows users (#1065)

* added alternative tts package for windows users

* fix function for determining OS
pull/1544/head
ggurdin 12 months ago committed by GitHub
parent 27e829380c
commit 007467d488
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,27 +3,35 @@ import 'dart:developer';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat/missing_voice_button.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart' as flutter_tts;
import 'package:matrix/matrix_api_lite/utils/logs.dart';
import 'package:text_to_speech/text_to_speech.dart';
class TtsController {
String? targetLanguage;
String? get targetLanguage =>
MatrixState.pangeaController.languageController.userL2?.langCode;
List<String> availableLangCodes = [];
final flutter_tts.FlutterTts tts = flutter_tts.FlutterTts();
List<String> _availableLangCodes = [];
final flutter_tts.FlutterTts _tts = flutter_tts.FlutterTts();
final TextToSpeech _alternativeTTS = TextToSpeech();
TtsController() {
setupTTS();
}
bool get _useAlternativeTTS {
return PlatformInfos.getOperatingSystem() == 'Windows';
}
Future<void> dispose() async {
await tts.stop();
await _tts.stop();
}
onError(dynamic message) => ErrorHandler.logError(
void _onError(dynamic message) => ErrorHandler.logError(
e: message,
m: (message.toString().isNotEmpty) ? message.toString() : 'TTS error',
data: {
@ -32,22 +40,23 @@ class TtsController {
);
Future<void> setupTTS() async {
try {
tts.setErrorHandler(onError);
targetLanguage ??=
MatrixState.pangeaController.languageController.userL2?.langCode;
if (_useAlternativeTTS) {
await _setupAltTTS();
return;
}
try {
_tts.setErrorHandler(_onError);
debugger(when: kDebugMode && targetLanguage == null);
tts.setLanguage(
_tts.setLanguage(
targetLanguage ?? "en",
);
await tts.awaitSpeakCompletion(true);
await _tts.awaitSpeakCompletion(true);
final voices = (await tts.getVoices) as List?;
availableLangCodes = (voices ?? [])
final voices = (await _tts.getVoices) as List?;
_availableLangCodes = (voices ?? [])
.map((v) {
// on iOS / web, the codes are in 'locale', but on Android, they are in 'name'
final nameCode = v['name']?.split("-").first;
@ -58,9 +67,34 @@ class TtsController {
.cast<String>()
.toList();
debugPrint("availableLangCodes: $availableLangCodes");
debugPrint("availableLangCodes: $_availableLangCodes");
debugger(when: kDebugMode && !isLanguageFullySupported);
debugger(when: kDebugMode && !_isLanguageFullySupported);
} catch (e, s) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: e, s: s);
}
}
Future<void> _setupAltTTS() async {
try {
final languages = await _alternativeTTS.getLanguages();
_availableLangCodes =
languages.map((lang) => lang.split("-").first).toSet().toList();
debugPrint("availableLangCodes: $_availableLangCodes");
final langsMatchingTarget = languages
.where(
(lang) =>
targetLanguage != null &&
lang.toLowerCase().startsWith(targetLanguage!.toLowerCase()),
)
.toList();
if (langsMatchingTarget.isNotEmpty) {
await _alternativeTTS.setLanguage(langsMatchingTarget.first);
}
} catch (e, s) {
debugger(when: kDebugMode);
ErrorHandler.logError(e: e, s: s);
@ -71,8 +105,10 @@ class TtsController {
try {
// return type is dynamic but apparent its supposed to be 1
// https://pub.dev/packages/flutter_tts
final result = await tts.stop();
if (result != 1) {
final result =
await (_useAlternativeTTS ? _alternativeTTS.stop() : _tts.stop());
if (!_useAlternativeTTS && result != 1) {
ErrorHandler.logError(
m: 'Unexpected result from tts.stop',
data: {
@ -86,7 +122,7 @@ class TtsController {
}
}
Future<void> showMissingVoicePopup(
Future<void> _showMissingVoicePopup(
BuildContext context,
String eventID,
) async {
@ -111,28 +147,29 @@ class TtsController {
BuildContext context,
String eventID,
) async {
if (isLanguageFullySupported) {
await speak(text);
if (_isLanguageFullySupported) {
await _speak(text);
} else {
ErrorHandler.logError(
e: 'Language not supported by TTS engine',
data: {
'targetLanguage': targetLanguage,
'availableLangCodes': availableLangCodes,
'availableLangCodes': _availableLangCodes,
},
);
await showMissingVoicePopup(context, eventID);
await _showMissingVoicePopup(context, eventID);
}
}
Future<void> speak(String text) async {
Future<void> _speak(String text) async {
try {
stop();
targetLanguage ??=
MatrixState.pangeaController.languageController.userL2?.langCode;
Logs().i('Speaking: $text');
final result = await tts.speak(text).timeout(
final result = await (_useAlternativeTTS
? _alternativeTTS.speak(text)
: _tts.speak(text))
.timeout(
const Duration(seconds: 5),
onTimeout: () {
ErrorHandler.logError(
@ -160,6 +197,6 @@ class TtsController {
}
}
bool get isLanguageFullySupported =>
availableLangCodes.contains(targetLanguage);
bool get _isLanguageFullySupported =>
_availableLangCodes.contains(targetLanguage);
}

@ -40,7 +40,7 @@ class WordAudioButtonState extends State<WordAudioButton> {
_isPlaying ? L10n.of(context)!.stop : L10n.of(context)!.playAudio,
onPressed: () async {
if (_isPlaying) {
await widget.ttsController.tts.stop();
await widget.ttsController.stop();
if (mounted) {
setState(() => _isPlaying = false);
}

@ -163,7 +163,11 @@ class WordFocusListeningActivityState
}
return GestureDetector(
onTap: () => tts.speak(widget.activityContent.choices[index]),
onTap: () => tts.tryToSpeak(
widget.activityContent.choices[index],
context,
widget.practiceCardController.widget.pangeaMessageEvent.eventId,
),
child: CircleAvatar(
radius: buttonSize,
backgroundColor: dragging ? Colors.grey.withOpacity(0.5) : buttonColor,

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher_string.dart';
import '../config/app_config.dart';
@ -86,4 +87,20 @@ abstract class PlatformInfos {
applicationName: AppConfig.applicationName,
);
}
// #Pangea
static String? getOperatingSystem() {
if (!kIsWeb) return null;
final String platform = html.window.navigator.platform?.toLowerCase() ?? '';
if (platform.contains('mac')) {
return 'macOS';
} else if (platform.contains('win')) {
return 'Windows';
} else if (platform.contains('linux')) {
return 'Linux';
}
return null;
}
// Pangea#
}

@ -36,6 +36,7 @@ import share_plus
import shared_preferences_foundation
import sqflite
import sqlcipher_flutter_libs
import text_to_speech_macos
import url_launcher_macos
import video_compress
import video_player_avfoundation
@ -74,6 +75,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
TextToSpeechMacOsPlugin.register(with: registry.registrar(forPlugin: "TextToSpeechMacOsPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))

@ -881,10 +881,10 @@ packages:
dependency: "direct main"
description:
name: flutter_tts
sha256: aed2a00c48c43af043ed81145fd8503ddd793dafa7088ab137dbef81a703e53d
sha256: cbec5f0447223e1b4c47f893c7f8ef663ac582120c147e4a1e2cade7f2e8b0c8
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.2.0"
flutter_typeahead:
dependency: "direct main"
description:
@ -2370,6 +2370,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.4"
text_to_speech:
dependency: "direct main"
description:
name: text_to_speech
sha256: f9adeb82bf0c912fd7f0ce656b1283e49b0869f9247bf865859dcf0186ed32f3
url: "https://pub.dev"
source: hosted
version: "0.2.3"
text_to_speech_macos:
dependency: transitive
description:
name: text_to_speech_macos
sha256: "11d1b7d4eff579743b04d371e86d17bebd599f7d998b9fa4cf07a5821cda3b6d"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
text_to_speech_platform_interface:
dependency: transitive
description:
name: text_to_speech_platform_interface
sha256: "9d637f0ae36e296f42a0e555bd65ba4c64a28a7c26a2752fdae62f6d78b6c2d0"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
text_to_speech_web:
dependency: transitive
description:
name: text_to_speech_web
sha256: "47d006c0a377c9eb3f6bcca4d92b3ece2c67f5eb31b9416727cc81b92c36d6d1"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
timezone:
dependency: transitive
description:

@ -6,7 +6,7 @@ description: Learn a language while texting your friends.
# Pangea#
publish_to: none
# On version bump also increase the build number for F-Droid
version: 1.23.10+3569
version: 1.23.11+3570
environment:
sdk: ">=3.0.0 <4.0.0"
@ -134,6 +134,7 @@ dependencies:
shimmer: ^3.0.0
syncfusion_flutter_xlsio: ^25.1.40
rive: 0.11.11
text_to_speech: ^0.2.3
flutter_tts: ^4.2.0
# Pangea#

Loading…
Cancel
Save