diff --git a/packages/flutter_tts/CHANGELOG.md b/packages/flutter_tts/CHANGELOG.md index ec827b00c..b4cd97d5e 100644 --- a/packages/flutter_tts/CHANGELOG.md +++ b/packages/flutter_tts/CHANGELOG.md @@ -1,6 +1,10 @@ -## NEXT +## 1.5.0 * Update minimum Flutter and Dart version to 3.13 and 3.1. +* Update flutter_tts 3.6.3 to 4.2.0. +* Update the example app. +* Add isLanguageAvailable, getVoices and setVoice APIs which were not implemented. + ## 1.4.0 diff --git a/packages/flutter_tts/README.md b/packages/flutter_tts/README.md index f2a6700a3..4d80b3067 100644 --- a/packages/flutter_tts/README.md +++ b/packages/flutter_tts/README.md @@ -10,8 +10,8 @@ This package is not an _endorsed_ implementation of `flutter_tts`. Therefore, yo ```yaml dependencies: - flutter_tts: ^3.6.3 - flutter_tts_tizen: ^1.4.0 + flutter_tts: ^4.2.0 + flutter_tts_tizen: ^1.5.0 ``` Then you can import `flutter_tts` in your Dart code: @@ -31,6 +31,9 @@ The features supported by Tizen are as follows. Other features are not supported - [x] pause - [x] get languages - [x] set language + - [x] is language available + - [x] get voices + - [x] set voice - [x] set speech rate - [x] set speech volume (requires privilege `http://tizen.org/privilege/volume.set` in `tizen_manifest.xml`) - [x] get default voice diff --git a/packages/flutter_tts/example/lib/main.dart b/packages/flutter_tts/example/lib/main.dart index ac268321c..8f95f460d 100644 --- a/packages/flutter_tts/example/lib/main.dart +++ b/packages/flutter_tts/example/lib/main.dart @@ -24,10 +24,10 @@ class _MyAppState extends State { TtsState ttsState = TtsState.stopped; - get isPlaying => ttsState == TtsState.playing; - get isStopped => ttsState == TtsState.stopped; - get isPaused => ttsState == TtsState.paused; - get isContinued => ttsState == TtsState.continued; + bool get isPlaying => ttsState == TtsState.playing; + bool get isStopped => ttsState == TtsState.stopped; + bool get isPaused => ttsState == TtsState.paused; + bool get isContinued => ttsState == TtsState.continued; @override initState() { @@ -35,13 +35,15 @@ class _MyAppState extends State { initTts(); } - initTts() { + dynamic initTts() { flutterTts = FlutterTts(); _setAwaitOptions(); _getDefaultVoice(); + _getSupportedVoices(); + flutterTts.setStartHandler(() { setState(() { print("Playing"); @@ -85,16 +87,23 @@ class _MyAppState extends State { }); } - Future _getLanguages() => flutterTts.getLanguages; + Future _getLanguages() async => await flutterTts.getLanguages; - Future _getDefaultVoice() async { + Future _getDefaultVoice() async { var voice = await flutterTts.getDefaultVoice; if (voice != null) { - print(voice); + print('_getDefaultVoice(): $voice'); } } - Future _speak() async { + Future _getSupportedVoices() async { + var voices = await flutterTts.getVoices; + if (voices != null) { + print('_getSupportedVoices(): $voices'); + } + } + + Future _speak() async { await flutterTts.setVolume(volume); await flutterTts.setSpeechRate(rate); @@ -105,16 +114,16 @@ class _MyAppState extends State { } } - Future _setAwaitOptions() async { + Future _setAwaitOptions() async { await flutterTts.awaitSpeakCompletion(true); } - Future _stop() async { + Future _stop() async { var result = await flutterTts.stop(); if (result == 1) setState(() => ttsState = TtsState.stopped); } - Future _pause() async { + Future _pause() async { var result = await flutterTts.pause(); if (result == 1) setState(() => ttsState = TtsState.paused); } @@ -126,16 +135,19 @@ class _MyAppState extends State { } List> getLanguageDropDownMenuItems( - dynamic languages) { + List languages) { var items = >[]; for (dynamic type in languages) { items.add(DropdownMenuItem( - value: type as String?, child: Text(type as String))); + value: type as String?, child: Text((type as String)))); } return items; } - void changedLanguageDropDownItem(String? selectedType) { + void changedLanguageDropDownItem(String? selectedType) async { + var result = await flutterTts.isLanguageAvailable(selectedType!); + if (result == 0) return; + setState(() { language = selectedType; flutterTts.setLanguage(language!); @@ -175,7 +187,7 @@ class _MyAppState extends State { future: _getLanguages(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { - return _languageDropDownSection(snapshot.data); + return _languageDropDownSection(snapshot.data as List); } else if (snapshot.hasError) { return Text('Error loading languages...'); } else @@ -191,6 +203,7 @@ class _MyAppState extends State { onChanged: (String value) { _onChange(value); }, + controller: TextEditingController(text: _newVoiceText), )); Widget _btnSection() { @@ -210,7 +223,7 @@ class _MyAppState extends State { ); } - Widget _languageDropDownSection(dynamic languages) => Container( + Widget _languageDropDownSection(List languages) => Container( padding: EdgeInsets.only(top: 10.0), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ DropdownButton( @@ -272,7 +285,7 @@ class _MyAppState extends State { min: 0.0, max: 1.0, divisions: 10, - label: "Volume: $volume"); + label: "Volume: ${volume.toStringAsFixed(1)}"); } Widget _rate() { @@ -284,7 +297,7 @@ class _MyAppState extends State { min: 0.0, max: 1.0, divisions: 10, - label: "Rate: $rate", + label: "Rate: ${rate.toStringAsFixed(1)}", activeColor: Colors.green, ); } diff --git a/packages/flutter_tts/example/pubspec.yaml b/packages/flutter_tts/example/pubspec.yaml index b383496ce..a5e126241 100644 --- a/packages/flutter_tts/example/pubspec.yaml +++ b/packages/flutter_tts/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_tts: ^3.6.3 + flutter_tts: ^4.2.0 flutter_tts_tizen: path: ../ diff --git a/packages/flutter_tts/pubspec.yaml b/packages/flutter_tts/pubspec.yaml index f2ee10183..107909294 100644 --- a/packages/flutter_tts/pubspec.yaml +++ b/packages/flutter_tts/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_tts_tizen description: The Tizen implementation of flutter_tts plugin. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/flutter_tts -version: 1.4.0 +version: 1.5.0 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/packages/flutter_tts/tizen/src/flutter_tts_tizen_plugin.cc b/packages/flutter_tts/tizen/src/flutter_tts_tizen_plugin.cc index 0e1153cd5..44e2a4a28 100644 --- a/packages/flutter_tts/tizen/src/flutter_tts_tizen_plugin.cc +++ b/packages/flutter_tts/tizen/src/flutter_tts_tizen_plugin.cc @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,19 @@ namespace { typedef flutter::MethodChannel FlMethodChannel; typedef flutter::MethodResult FlMethodResult; +template +bool GetValueFromEncodableMap(flutter::EncodableMap &map, std::string key, + T &out) { + auto iter = map.find(flutter::EncodableValue(key)); + if (iter != map.end() && !iter->second.IsNull()) { + if (auto pval = std::get_if(&iter->second)) { + out = *pval; + return true; + } + } + return false; +} + class FlutterTtsTizenPlugin : public flutter::Plugin { public: static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar) { @@ -103,13 +117,19 @@ class FlutterTtsTizenPlugin : public flutter::Plugin { } else if (method_name == "setLanguage") { OnSetLanguage(arguments); } else if (method_name == "getLanguages") { - OnGetLanguage(); + OnGetLanguages(); } else if (method_name == "getDefaultVoice") { OnGetDefaultVoice(); + } else if (method_name == "setVoice") { + OnSetVoice(arguments); + } else if (method_name == "getVoices") { + OnGetVoices(); } else if (method_name == "getMaxSpeechInputLength") { OnGetMaxSpeechInputLength(); } else if (method_name == "setVolume") { OnSetVolume(arguments); + } else if (method_name == "isLanguageAvailable") { + OnIsLanguageAvailable(arguments); } else { result_->NotImplemented(); } @@ -195,7 +215,7 @@ class FlutterTtsTizenPlugin : public flutter::Plugin { SendResult(flutter::EncodableValue(0)); } - void OnGetLanguage() { + void OnGetLanguages() { flutter::EncodableList list; for (auto language : tts_->GetSupportedLanaguages()) { list.push_back(flutter::EncodableValue(language)); @@ -219,6 +239,35 @@ class FlutterTtsTizenPlugin : public flutter::Plugin { SendResult(flutter::EncodableValue(default_voice_map)); } + void OnSetVoice(const flutter::EncodableValue &arguments) { + if (std::holds_alternative(arguments)) { + auto voice = std::get(arguments); + + std::string voice_name; + if (GetValueFromEncodableMap(voice, "name", voice_name)) { + tts_->SetDefaultVoiceType(voice_name); + SendResult(flutter::EncodableValue(1)); + return; + } + } + SendResult(flutter::EncodableValue(0)); + } + + void OnGetVoices() { + flutter::EncodableList list; + for (auto voice : tts_->GetSupportedVoiceTypes()) { + flutter::EncodableMap map; + map.insert(std::pair( + flutter::EncodableValue("name"), + flutter::EncodableValue(voice["name"]))); + map.insert(std::pair( + flutter::EncodableValue("locale"), + flutter::EncodableValue(voice["locale"]))); + list.push_back(flutter::EncodableValue(map)); + } + SendResult(flutter::EncodableValue(list)); + } + void OnGetMaxSpeechInputLength() { std::optional length = tts_->GetMaxSpeechInputLength(); SendResult(length.has_value() ? flutter::EncodableValue(*length) @@ -236,6 +285,17 @@ class FlutterTtsTizenPlugin : public flutter::Plugin { SendResult(flutter::EncodableValue(0)); } + void OnIsLanguageAvailable(const flutter::EncodableValue &arguments) { + if (std::holds_alternative(arguments)) { + std::string language = std::move(std::get(arguments)); + if (!language.empty() && tts_->IsLanguageAvailable(language)) { + SendResult(flutter::EncodableValue(1)); + return; + } + } + SendResult(flutter::EncodableValue(0)); + } + void SendResult(const flutter::EncodableValue &result) { if (result_) { result_->Success(result); diff --git a/packages/flutter_tts/tizen/src/text_to_speech.cc b/packages/flutter_tts/tizen/src/text_to_speech.cc index 095fa6029..ff70e2d9c 100644 --- a/packages/flutter_tts/tizen/src/text_to_speech.cc +++ b/packages/flutter_tts/tizen/src/text_to_speech.cc @@ -10,6 +10,35 @@ namespace { +std::string GetVoiceName(int32_t voice_type) { + switch (voice_type) { + case TTS_VOICE_TYPE_AUTO: + return "auto"; + case TTS_VOICE_TYPE_MALE: + return "male"; + case TTS_VOICE_TYPE_FEMALE: + return "female"; + case TTS_VOICE_TYPE_CHILD: + return "child"; + default: + return "unknown"; + } +} + +int32_t GetVoiceType(std::string voice_name) { + if (voice_name == "auto") { + return TTS_VOICE_TYPE_AUTO; + } else if (voice_name == "male") { + return TTS_VOICE_TYPE_MALE; + } else if (voice_name == "female") { + return TTS_VOICE_TYPE_FEMALE; + } else if (voice_name == "child") { + return TTS_VOICE_TYPE_CHILD; + } else { + return -1; + } +} + TtsState ConvertTtsState(tts_state_e state) { switch (state) { case TTS_STATE_CREATED: @@ -44,6 +73,7 @@ bool TextToSpeech::Initialize() { RegisterCallbacks(); Prepare(); + InitializeSupportedLanaguagesAndVoiceType(); return true; } @@ -107,28 +137,27 @@ void TextToSpeech::UnregisterCallbacks() { tts_unset_error_cb(tts_); } -std::vector &TextToSpeech::GetSupportedLanaguages() { - if (supported_lanaguages_.size() == 0) { - tts_foreach_supported_voices( - tts_, - [](tts_h tts, const char *language, int32_t voice_type, - void *user_data) -> bool { - if (language == nullptr) { - return false; - } - TextToSpeech *self = static_cast(user_data); - self->supported_lanaguages_.push_back(std::string(language)); - LOG_INFO("Supported voice: language(%s), type(%d)", language, - voice_type); - return true; - }, - this); - - supported_lanaguages_.erase( - unique(supported_lanaguages_.begin(), supported_lanaguages_.end()), - supported_lanaguages_.end()); - } - return supported_lanaguages_; +void TextToSpeech::InitializeSupportedLanaguagesAndVoiceType() { + tts_foreach_supported_voices( + tts_, + [](tts_h tts, const char *language, int32_t voice_type, + void *user_data) -> bool { + if (language == nullptr) { + return false; + } + TextToSpeech *self = static_cast(user_data); + self->supported_lanaguages_.push_back(language); + + std::map item; + item["name"] = GetVoiceName(voice_type); + item["locale"] = language; + self->supported_voice_types_.push_back(item); + + LOG_INFO("Supported language: %s, voice_type: %s", + item["locale"].c_str(), item["name"].c_str()); + return true; + }, + this); } std::optional> @@ -147,25 +176,15 @@ TextToSpeech::GetDefaultVoice() { default_voice.first = language; free(language); } + default_voice.second = GetVoiceName(voice_type); + return default_voice; +} - switch (voice_type) { - case TTS_VOICE_TYPE_AUTO: - default_voice.second = "auto"; - break; - case TTS_VOICE_TYPE_MALE: - default_voice.second = "male"; - break; - case TTS_VOICE_TYPE_FEMALE: - default_voice.second = "female"; - break; - case TTS_VOICE_TYPE_CHILD: - default_voice.second = "child"; - break; - default: - default_voice.second = "unknown"; - break; +void TextToSpeech::SetDefaultVoiceType(const std::string &voice) { + int32_t voice_type = GetVoiceType(voice); + if (voice_type != -1) { + default_voice_type_ = voice_type; } - return default_voice; } std::optional TextToSpeech::GetMaxSpeechInputLength() { @@ -245,6 +264,11 @@ bool TextToSpeech::GetSpeedRange(int32_t *min, int32_t *normal, int32_t *max) { return true; } +bool TextToSpeech::IsLanguageAvailable(const std::string &language) { + return std::find(supported_lanaguages_.begin(), supported_lanaguages_.end(), + language) != supported_lanaguages_.end(); +} + void TextToSpeech::SwitchVolumeOnStateChange(tts_state_e previous, tts_state_e current) { if (previous == TTS_STATE_PLAYING) { diff --git a/packages/flutter_tts/tizen/src/text_to_speech.h b/packages/flutter_tts/tizen/src/text_to_speech.h index a860a548e..633cbaee3 100644 --- a/packages/flutter_tts/tizen/src/text_to_speech.h +++ b/packages/flutter_tts/tizen/src/text_to_speech.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -40,10 +41,21 @@ class TextToSpeech { default_language_ = language; } - std::vector &GetSupportedLanaguages(); + void InitializeSupportedLanaguagesAndVoiceType(); + + const std::vector &GetSupportedLanaguages() { + return supported_lanaguages_; + } + + const std::vector> + &GetSupportedVoiceTypes() { + return supported_voice_types_; + } std::optional> GetDefaultVoice(); + void SetDefaultVoiceType(const std::string &voice); + std::optional GetMaxSpeechInputLength(); std::optional GetState(); @@ -58,6 +70,8 @@ class TextToSpeech { bool SetVolume(double volume); + bool IsLanguageAvailable(const std::string &language); + bool GetSpeedRange(int32_t *min, int32_t *normal, int32_t *max); void SetTtsSpeed(int32_t speed) { tts_speed_ = speed; } @@ -84,6 +98,7 @@ class TextToSpeech { int32_t system_volume_ = 0; int32_t system_max_volume_ = 0; std::vector supported_lanaguages_; + std::vector> supported_voice_types_; StateChangedCallback state_changed_callback_; UtteranceCompletedCallback utterance_completed_callback_;