Skip to content

Commit a0c7127

Browse files
committed
Merge pull request #109162 from bruvzg/android_tts
Fix Android TTS on-demand init.
2 parents c720789 + 4915d61 commit a0c7127

File tree

3 files changed

+138
-21
lines changed

3 files changed

+138
-21
lines changed

platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,21 @@
5656
* </ul>
5757
*/
5858
@Keep
59-
public class GodotTTS extends UtteranceProgressListener {
59+
public class GodotTTS extends UtteranceProgressListener implements TextToSpeech.OnInitListener {
6060
// Note: These constants must be in sync with DisplayServer::TTSUtteranceEvent enum from "servers/display_server.h".
6161
final private static int EVENT_START = 0;
6262
final private static int EVENT_END = 1;
6363
final private static int EVENT_CANCEL = 2;
6464
final private static int EVENT_BOUNDARY = 3;
6565

66+
// Note: These constants must be in sync with TTS_Android constants from "platform/android/tts_android.h".
67+
final private static int INIT_STATE_UNKNOWN = 0;
68+
final private static int INIT_STATE_SUCCESS = 1;
69+
final private static int INIT_STATE_FAIL = -1;
70+
6671
private final Context context;
6772
private TextToSpeech synth;
73+
private int state;
6874
private LinkedList<GodotUtterance> queue;
6975
final private Object lock = new Object();
7076
private GodotUtterance lastUtterance;
@@ -82,6 +88,9 @@ private void updateTTS() {
8288
GodotUtterance message = queue.pollFirst();
8389

8490
Set<Voice> voices = synth.getVoices();
91+
if (voices == null) {
92+
return;
93+
}
8594
for (Voice v : voices) {
8695
if (v.getName().equals(message.voice)) {
8796
synth.setVoice(v);
@@ -189,17 +198,35 @@ public void onError(String utteranceId) {
189198
* Initialize synth and query.
190199
*/
191200
public void init() {
192-
synth = new TextToSpeech(context, null);
201+
state = INIT_STATE_UNKNOWN;
202+
synth = new TextToSpeech(context, this);
193203
queue = new LinkedList<GodotUtterance>();
194204

195205
synth.setOnUtteranceProgressListener(this);
196206
}
197207

208+
/**
209+
* Called by TTS engine when initialization is finished.
210+
*/
211+
@Override
212+
public void onInit(int status) {
213+
synchronized (lock) {
214+
if (status == TextToSpeech.SUCCESS) {
215+
state = INIT_STATE_SUCCESS;
216+
} else {
217+
state = INIT_STATE_FAIL;
218+
}
219+
}
220+
}
221+
198222
/**
199223
* Adds an utterance to the queue.
200224
*/
201225
public void speak(String text, String voice, int volume, float pitch, float rate, int utterance_id, boolean interrupt) {
202226
synchronized (lock) {
227+
if (state != INIT_STATE_SUCCESS) {
228+
return;
229+
}
203230
GodotUtterance message = new GodotUtterance(text, voice, volume, pitch, rate, utterance_id);
204231
queue.addLast(message);
205232

@@ -216,6 +243,9 @@ public void speak(String text, String voice, int volume, float pitch, float rate
216243
*/
217244
public void pauseSpeaking() {
218245
synchronized (lock) {
246+
if (state != INIT_STATE_SUCCESS) {
247+
return;
248+
}
219249
if (!paused) {
220250
paused = true;
221251
synth.stop();
@@ -228,10 +258,16 @@ public void pauseSpeaking() {
228258
*/
229259
public void resumeSpeaking() {
230260
synchronized (lock) {
261+
if (state != INIT_STATE_SUCCESS) {
262+
return;
263+
}
231264
if (lastUtterance != null && paused) {
232265
int mode = TextToSpeech.QUEUE_FLUSH;
233266

234267
Set<Voice> voices = synth.getVoices();
268+
if (voices == null) {
269+
return;
270+
}
235271
for (Voice v : voices) {
236272
if (v.getName().equals(lastUtterance.voice)) {
237273
synth.setVoice(v);
@@ -261,6 +297,9 @@ public void resumeSpeaking() {
261297
*/
262298
public void stopSpeaking() {
263299
synchronized (lock) {
300+
if (state != INIT_STATE_SUCCESS) {
301+
return;
302+
}
264303
for (GodotUtterance u : queue) {
265304
GodotLib.ttsCallback(EVENT_CANCEL, u.id, 0);
266305
}
@@ -282,13 +321,21 @@ public void stopSpeaking() {
282321
* Returns voice information.
283322
*/
284323
public String[] getVoices() {
285-
Set<Voice> voices = synth.getVoices();
286-
String[] list = new String[voices.size()];
287-
int i = 0;
288-
for (Voice v : voices) {
289-
list[i++] = v.getLocale().toString() + ";" + v.getName();
324+
synchronized (lock) {
325+
if (state != INIT_STATE_SUCCESS) {
326+
return new String[0];
327+
}
328+
Set<Voice> voices = synth.getVoices();
329+
if (voices == null) {
330+
return new String[0];
331+
}
332+
String[] list = new String[voices.size()];
333+
int i = 0;
334+
for (Voice v : voices) {
335+
list[i++] = v.getLocale().toString() + ";" + v.getName();
336+
}
337+
return list;
290338
}
291-
return list;
292339
}
293340

294341
/**
@@ -304,4 +351,13 @@ public boolean isSpeaking() {
304351
public boolean isPaused() {
305352
return paused;
306353
}
354+
355+
/**
356+
* Returns INIT_STATE_SUCCESS if the synthesizer initialization finished successfully, INIT_STATE_FAIL if initialization failed, and INIT_STATE_UNKNOWN otherwise.
357+
*/
358+
public int getState() {
359+
synchronized (lock) {
360+
return state;
361+
}
362+
}
307363
}

platform/android/tts_android.cpp

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,14 @@ bool TTS_Android::initialized = false;
3838
jobject TTS_Android::tts = nullptr;
3939
jclass TTS_Android::cls = nullptr;
4040

41+
Thread TTS_Android::init_thread;
42+
SafeFlag TTS_Android::quit_request;
43+
SafeFlag TTS_Android::init_done;
44+
4145
jmethodID TTS_Android::_init = nullptr;
4246
jmethodID TTS_Android::_is_speaking = nullptr;
4347
jmethodID TTS_Android::_is_paused = nullptr;
48+
jmethodID TTS_Android::_get_state = nullptr;
4449
jmethodID TTS_Android::_get_voices = nullptr;
4550
jmethodID TTS_Android::_speak = nullptr;
4651
jmethodID TTS_Android::_pause_speaking = nullptr;
@@ -49,12 +54,49 @@ jmethodID TTS_Android::_stop_speaking = nullptr;
4954

5055
HashMap<int, Char16String> TTS_Android::ids;
5156

52-
void TTS_Android::initialize_tts() {
57+
void TTS_Android::_thread_function(void *self) {
58+
JNIEnv *env = get_jni_env();
59+
ERR_FAIL_NULL(env);
60+
61+
env->CallVoidMethod(tts, _init);
62+
63+
uint64_t sleep = 200;
64+
while (env->CallIntMethod(tts, _get_state) == INIT_STATE_UNKNOWN && !quit_request.is_set()) {
65+
OS::get_singleton()->delay_usec(1000 * sleep);
66+
}
67+
init_done.set();
68+
}
69+
70+
void TTS_Android::initialize_tts(bool p_wait) {
71+
if (!_init || !_get_state || !tts) {
72+
return;
73+
}
5374
JNIEnv *env = get_jni_env();
5475
ERR_FAIL_NULL(env);
5576

56-
if (_init) {
57-
env->CallVoidMethod(tts, _init);
77+
if (!init_thread.is_started() && !init_done.is_set()) {
78+
init_thread.start(TTS_Android::_thread_function, nullptr);
79+
}
80+
81+
if (env->CallIntMethod(tts, _get_state) == INIT_STATE_SUCCESS) {
82+
initialized = true;
83+
return;
84+
}
85+
86+
// If it's not initialized at launch wait for 1 second for TTS init.
87+
if (p_wait) {
88+
uint64_t sleep = 200;
89+
uint64_t wait = 1000000;
90+
uint64_t time = OS::get_singleton()->get_ticks_usec();
91+
while (OS::get_singleton()->get_ticks_usec() - time < wait) {
92+
OS::get_singleton()->delay_usec(1000 * sleep);
93+
if (init_done.is_set()) {
94+
break;
95+
}
96+
}
97+
}
98+
99+
if (env->CallIntMethod(tts, _get_state) == INIT_STATE_SUCCESS) {
58100
initialized = true;
59101
}
60102
}
@@ -64,13 +106,16 @@ void TTS_Android::setup(jobject p_tts) {
64106
ERR_FAIL_NULL(env);
65107

66108
tts = env->NewGlobalRef(p_tts);
109+
quit_request.clear();
110+
init_done.clear();
67111

68112
jclass c = env->GetObjectClass(tts);
69113
cls = (jclass)env->NewGlobalRef(c);
70114

71115
_init = env->GetMethodID(cls, "init", "()V");
72116
_is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
73117
_is_paused = env->GetMethodID(cls, "isPaused", "()Z");
118+
_get_state = env->GetMethodID(cls, "getState", "()I");
74119
_get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
75120
_speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
76121
_pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
@@ -79,14 +124,19 @@ void TTS_Android::setup(jobject p_tts) {
79124

80125
bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech");
81126
if (tts_enabled) {
82-
initialize_tts();
127+
initialize_tts(false);
83128
}
84129
}
85130

86131
void TTS_Android::terminate() {
87132
JNIEnv *env = get_jni_env();
88133
ERR_FAIL_NULL(env);
89134

135+
if (init_thread.is_started()) {
136+
quit_request.set();
137+
init_thread.wait_to_finish();
138+
}
139+
90140
if (cls) {
91141
env->DeleteGlobalRef(cls);
92142
}
@@ -99,7 +149,7 @@ void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
99149
if (unlikely(!initialized)) {
100150
initialize_tts();
101151
}
102-
ERR_FAIL_NULL(tts);
152+
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
103153
if (ids.has(p_id)) {
104154
int pos = 0;
105155
if ((DisplayServer::TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
@@ -123,7 +173,7 @@ bool TTS_Android::is_speaking() {
123173
if (unlikely(!initialized)) {
124174
initialize_tts();
125175
}
126-
ERR_FAIL_NULL_V(tts, false);
176+
ERR_FAIL_COND_V_MSG(!initialized || tts == nullptr, false, "Text to Speech not initialized.");
127177
if (_is_speaking) {
128178
JNIEnv *env = get_jni_env();
129179

@@ -138,7 +188,7 @@ bool TTS_Android::is_paused() {
138188
if (unlikely(!initialized)) {
139189
initialize_tts();
140190
}
141-
ERR_FAIL_NULL_V(tts, false);
191+
ERR_FAIL_COND_V_MSG(!initialized || tts == nullptr, false, "Text to Speech not initialized.");
142192
if (_is_paused) {
143193
JNIEnv *env = get_jni_env();
144194

@@ -153,7 +203,7 @@ Array TTS_Android::get_voices() {
153203
if (unlikely(!initialized)) {
154204
initialize_tts();
155205
}
156-
ERR_FAIL_NULL_V(tts, Array());
206+
ERR_FAIL_COND_V_MSG(!initialized || tts == nullptr, Array(), "Text to Speech not initialized.");
157207
Array list;
158208
if (_get_voices) {
159209
JNIEnv *env = get_jni_env();
@@ -184,7 +234,7 @@ void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volum
184234
if (unlikely(!initialized)) {
185235
initialize_tts();
186236
}
187-
ERR_FAIL_NULL(tts);
237+
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
188238
if (p_interrupt) {
189239
stop();
190240
}
@@ -212,7 +262,7 @@ void TTS_Android::pause() {
212262
if (unlikely(!initialized)) {
213263
initialize_tts();
214264
}
215-
ERR_FAIL_NULL(tts);
265+
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
216266
if (_pause_speaking) {
217267
JNIEnv *env = get_jni_env();
218268

@@ -225,7 +275,7 @@ void TTS_Android::resume() {
225275
if (unlikely(!initialized)) {
226276
initialize_tts();
227277
}
228-
ERR_FAIL_NULL(tts);
278+
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
229279
if (_resume_speaking) {
230280
JNIEnv *env = get_jni_env();
231281

@@ -238,7 +288,7 @@ void TTS_Android::stop() {
238288
if (unlikely(!initialized)) {
239289
initialize_tts();
240290
}
241-
ERR_FAIL_NULL(tts);
291+
ERR_FAIL_COND_MSG(!initialized || tts == nullptr, "Text to Speech not initialized.");
242292
for (const KeyValue<int, Char16String> &E : ids) {
243293
DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
244294
}

platform/android/tts_android.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,33 @@
3939
#include <jni.h>
4040

4141
class TTS_Android {
42+
static inline int INIT_STATE_UNKNOWN = 0;
43+
static inline int INIT_STATE_SUCCESS = 1;
44+
static inline int INIT_STATE_FAIL = -1;
45+
4246
static bool initialized;
4347
static jobject tts;
4448
static jclass cls;
4549

4650
static jmethodID _init;
4751
static jmethodID _is_speaking;
4852
static jmethodID _is_paused;
53+
static jmethodID _get_state;
4954
static jmethodID _get_voices;
5055
static jmethodID _speak;
5156
static jmethodID _pause_speaking;
5257
static jmethodID _resume_speaking;
5358
static jmethodID _stop_speaking;
5459

60+
static Thread init_thread;
61+
static SafeFlag quit_request;
62+
static SafeFlag init_done;
63+
64+
static void _thread_function(void *self);
65+
5566
static HashMap<int, Char16String> ids;
5667

57-
static void initialize_tts();
68+
static void initialize_tts(bool p_wait = true);
5869

5970
public:
6071
static void setup(jobject p_tts);

0 commit comments

Comments
 (0)