From be5497701a8bd3bba1a846391b46b1d576a15d6e Mon Sep 17 00:00:00 2001 From: Alejandro Ulate Date: Mon, 23 Aug 2021 17:12:40 -0600 Subject: [PATCH 1/5] feat: adds voice gender and voice class --- .../gradle/wrapper/gradle-wrapper.properties | 6 +-- .../fluttertts/FlutterTtsPlugin.java | 4 ++ .../org.eclipse.buildship.core.prefs | 13 ++++- example/android/app/.classpath | 4 +- ios/Classes/SwiftFlutterTtsPlugin.swift | 3 ++ lib/flutter_tts.dart | 13 +++-- lib/models/models.dart | 1 + lib/models/tts_voice.dart | 53 +++++++++++++++++++ macos/Classes/FlutterTtsPlugin.swift | 3 ++ 9 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 lib/models/models.dart create mode 100644 lib/models/tts_voice.dart diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 8d9c8a3f..046fd486 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Sep 27 12:18:40 PDT 2019 +#Mon Aug 23 14:56:56 CST 2021 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/android/src/main/java/com/tundralabs/fluttertts/FlutterTtsPlugin.java b/android/src/main/java/com/tundralabs/fluttertts/FlutterTtsPlugin.java index 817ed2ed..0b7db99c 100644 --- a/android/src/main/java/com/tundralabs/fluttertts/FlutterTtsPlugin.java +++ b/android/src/main/java/com/tundralabs/fluttertts/FlutterTtsPlugin.java @@ -475,6 +475,10 @@ void getVoices(Result result) { HashMap voiceMap = new HashMap<>(); voiceMap.put("name", voice.getName()); voiceMap.put("locale", voice.getLocale().toLanguageTag()); + + String gender = voice.getName().contains("female") ? "female" + : voice.getName().contains("male") ? "male" : "unspecified"; + voiceMap.put("gender", gender); voices.add(voiceMap); } result.success(voices); diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs index 9f105273..75b290b0 100644 --- a/example/android/.settings/org.eclipse.buildship.core.prefs +++ b/example/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ -#Sat Apr 14 11:55:40 PDT 2018 +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.0-rc-1)) connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/example/android/app/.classpath b/example/android/app/.classpath index 8d8d85f1..4a04201c 100644 --- a/example/android/app/.classpath +++ b/example/android/app/.classpath @@ -1,6 +1,6 @@ - + - + diff --git a/ios/Classes/SwiftFlutterTtsPlugin.swift b/ios/Classes/SwiftFlutterTtsPlugin.swift index 0e36019a..363648e3 100644 --- a/ios/Classes/SwiftFlutterTtsPlugin.swift +++ b/ios/Classes/SwiftFlutterTtsPlugin.swift @@ -318,6 +318,9 @@ public class SwiftFlutterTtsPlugin: NSObject, FlutterPlugin, AVSpeechSynthesizer for voice in AVSpeechSynthesisVoice.speechVoices() { voiceDict["name"] = voice.name voiceDict["locale"] = voice.language + let gender = voice.gender == AVSpeechSynthesisVoiceGender.female ? "female" + : voice.gender == AVSpeechSynthesisVoiceGender.male ? "male" : "unspecified" + voiceDict["gender"] = gender voices.add(voiceDict) } result(voices) diff --git a/lib/flutter_tts.dart b/lib/flutter_tts.dart index 1ccbebcb..33734aa8 100644 --- a/lib/flutter_tts.dart +++ b/lib/flutter_tts.dart @@ -3,6 +3,7 @@ import 'dart:io' show Platform; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_tts/models/tts_voice.dart'; typedef void ErrorHandler(dynamic message); typedef ProgressHandler = void Function( @@ -241,10 +242,16 @@ class FlutterTts { } /// [Future] which invokes the platform specific method for getVoices - /// Returns a `List` of `Maps` containing a voice name and locale + /// Returns a `List` of [TTSVoice] containing a voice object /// ***Android, iOS, and macOS supported only*** - Future get getVoices async { - final voices = await _channel.invokeMethod('getVoices'); + Future> get getVoices async { + final voicesObjs = await _channel.invokeMethod('getVoices') as List; + // Parse voices to class. + final voices = voicesObjs.map((Object? voiceObj) { + final voiceMap = voiceObj as Map?; + + return TTSVoice.fromMap(voiceMap!); + }).toList(); return voices; } diff --git a/lib/models/models.dart b/lib/models/models.dart new file mode 100644 index 00000000..7a306cd6 --- /dev/null +++ b/lib/models/models.dart @@ -0,0 +1 @@ +export 'tts_voice.dart'; diff --git a/lib/models/tts_voice.dart b/lib/models/tts_voice.dart new file mode 100644 index 00000000..3c8da3da --- /dev/null +++ b/lib/models/tts_voice.dart @@ -0,0 +1,53 @@ +/// {@template tts_voice} +/// Represents a wapper around Android's Voice class and +/// Swift's AVSpeechSynthesisVoice class. +/// +/// It exposes basic information about the underlying classes like the name, +/// locale and gender of the synthetic voice. +/// +/// {@endtemplate} +class TTSVoice { + /// {@macro tts_voice} + const TTSVoice({ + required this.name, + required this.locale, + required this.gender, + }); + + TTSVoice.fromMap(Map map) + : this.name = map['name']!.toString(), + this.locale = map['locale']!.toString(), + this.gender = TTSVoiceGenderFromString.fromString( + map['gender']!.toString(), + ); + + /// The tts_voice's name. + final String name; + + /// The tts_voice's locale. + final String locale; + + /// The tts_voice's gender. + final TTSVoiceGender gender; +} + +enum TTSVoiceGender { + male, + female, + unspecified, +} + +extension TTSVoiceGenderFromString on TTSVoiceGender { + static TTSVoiceGender fromString(String value) { + switch (value) { + case "female": + return TTSVoiceGender.female; + + case "male": + return TTSVoiceGender.male; + + default: + return TTSVoiceGender.unspecified; + } + } +} diff --git a/macos/Classes/FlutterTtsPlugin.swift b/macos/Classes/FlutterTtsPlugin.swift index 0c5af1ad..36074bc0 100644 --- a/macos/Classes/FlutterTtsPlugin.swift +++ b/macos/Classes/FlutterTtsPlugin.swift @@ -263,6 +263,9 @@ public class FlutterTtsPlugin: NSObject, FlutterPlugin, AVSpeechSynthesizerDeleg for voice in AVSpeechSynthesisVoice.speechVoices() { voiceDict["name"] = voice.name voiceDict["locale"] = voice.language + let gender = voice.gender == AVSpeechSynthesisVoiceGender.female ? "female" + : voice.gender == AVSpeechSynthesisVoiceGender.male ? "male" : "unspecified" + voiceDict["gender"] = gender voices.add(voiceDict) } result(voices) From 31995f36dc5b0aee59ffaab036cdb08c076b31ab Mon Sep 17 00:00:00 2001 From: Alejandro Ulate Date: Mon, 23 Aug 2021 17:21:49 -0600 Subject: [PATCH 2/5] fix: adds export of models to the lib --- lib/flutter_tts.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/flutter_tts.dart b/lib/flutter_tts.dart index 33734aa8..b94ef538 100644 --- a/lib/flutter_tts.dart +++ b/lib/flutter_tts.dart @@ -3,7 +3,9 @@ import 'dart:io' show Platform; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_tts/models/tts_voice.dart'; +import 'package:flutter_tts/models/models.dart'; + +export 'package:flutter_tts/models/models.dart'; typedef void ErrorHandler(dynamic message); typedef ProgressHandler = void Function( From f89f56a51c94286028de988b7e4e50d28be0d398 Mon Sep 17 00:00:00 2001 From: Alejandro Ulate Date: Mon, 23 Aug 2021 17:26:26 -0600 Subject: [PATCH 3/5] feat: adds basic usage booleans --- lib/models/tts_voice.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/models/tts_voice.dart b/lib/models/tts_voice.dart index 3c8da3da..3ee1c069 100644 --- a/lib/models/tts_voice.dart +++ b/lib/models/tts_voice.dart @@ -50,4 +50,13 @@ extension TTSVoiceGenderFromString on TTSVoiceGender { return TTSVoiceGender.unspecified; } } + + /// Helper to determine if value is male. + bool get isMale => this == TTSVoiceGender.male; + + /// Helper to determine if value is female. + bool get isFemale => this == TTSVoiceGender.female; + + /// Helper to determine if value is unspecified. + bool get isUnspecified => this == TTSVoiceGender.unspecified; } From 38124222a074a8dc52b830511ec3445bf80c4663 Mon Sep 17 00:00:00 2001 From: Alejandro Ulate Date: Mon, 23 Aug 2021 18:17:08 -0600 Subject: [PATCH 4/5] fix: set voice with model instead of map --- lib/flutter_tts.dart | 4 ++-- lib/models/tts_voice.dart | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/flutter_tts.dart b/lib/flutter_tts.dart index b94ef538..47ae6921 100644 --- a/lib/flutter_tts.dart +++ b/lib/flutter_tts.dart @@ -213,8 +213,8 @@ class FlutterTts { /// [Future] which invokes the platform specific method for setVoice /// ***Android, iOS, and macOS supported only*** - Future setVoice(Map voice) async => - _channel.invokeMethod('setVoice', voice); + Future setVoice(TTSVoice voice) async => + _channel.invokeMethod('setVoice', voice.asVoiceMap); /// [Future] which invokes the platform specific method for stop Future stop() async => _channel.invokeMethod('stop'); diff --git a/lib/models/tts_voice.dart b/lib/models/tts_voice.dart index 3ee1c069..f15766f1 100644 --- a/lib/models/tts_voice.dart +++ b/lib/models/tts_voice.dart @@ -29,6 +29,11 @@ class TTSVoice { /// The tts_voice's gender. final TTSVoiceGender gender; + + Map get asVoiceMap => { + 'name': name, + 'locale': locale, + }; } enum TTSVoiceGender { From 6d864c5867627786b1b553a393e03ee7adc838ae Mon Sep 17 00:00:00 2001 From: Alejandro Ulate Date: Mon, 23 Aug 2021 19:04:32 -0600 Subject: [PATCH 5/5] fix: fixes issues around ios version checking for gender --- ios/Classes/SwiftFlutterTtsPlugin.swift | 12 +++++++++--- macos/Classes/FlutterTtsPlugin.swift | 10 +++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ios/Classes/SwiftFlutterTtsPlugin.swift b/ios/Classes/SwiftFlutterTtsPlugin.swift index 363648e3..904b5a36 100644 --- a/ios/Classes/SwiftFlutterTtsPlugin.swift +++ b/ios/Classes/SwiftFlutterTtsPlugin.swift @@ -318,9 +318,15 @@ public class SwiftFlutterTtsPlugin: NSObject, FlutterPlugin, AVSpeechSynthesizer for voice in AVSpeechSynthesisVoice.speechVoices() { voiceDict["name"] = voice.name voiceDict["locale"] = voice.language - let gender = voice.gender == AVSpeechSynthesisVoiceGender.female ? "female" - : voice.gender == AVSpeechSynthesisVoiceGender.male ? "male" : "unspecified" - voiceDict["gender"] = gender + + if #available(iOS 13.0, *) { + let gender = voice.gender == AVSpeechSynthesisVoiceGender.female ? "female" + : voice.gender == AVSpeechSynthesisVoiceGender.male ? "male" : "unspecified" + voiceDict["gender"] = gender + } else { + voiceDict["gender"] = "unspecified" + } + voices.add(voiceDict) } result(voices) diff --git a/macos/Classes/FlutterTtsPlugin.swift b/macos/Classes/FlutterTtsPlugin.swift index 36074bc0..e456833b 100644 --- a/macos/Classes/FlutterTtsPlugin.swift +++ b/macos/Classes/FlutterTtsPlugin.swift @@ -263,9 +263,13 @@ public class FlutterTtsPlugin: NSObject, FlutterPlugin, AVSpeechSynthesizerDeleg for voice in AVSpeechSynthesisVoice.speechVoices() { voiceDict["name"] = voice.name voiceDict["locale"] = voice.language - let gender = voice.gender == AVSpeechSynthesisVoiceGender.female ? "female" - : voice.gender == AVSpeechSynthesisVoiceGender.male ? "male" : "unspecified" - voiceDict["gender"] = gender + if #available(iOS 13.0, *) { + let gender = voice.gender == AVSpeechSynthesisVoiceGender.female ? "female" + : voice.gender == AVSpeechSynthesisVoiceGender.male ? "male" : "unspecified" + voiceDict["gender"] = gender + } else { + voiceDict["gender"] = "unspecified" + } voices.add(voiceDict) } result(voices)