@iternio/react-native-tts 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +268 -0
- package/TextToSpeech.podspec +21 -0
- package/android/build.gradle +41 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/net/no_mad/tts/TextToSpeechModule.java +538 -0
- package/android/src/main/java/net/no_mad/tts/TextToSpeechPackage.java +31 -0
- package/index.d.ts +118 -0
- package/index.js +127 -0
- package/ios/TextToSpeech/TextToSpeech.h +20 -0
- package/ios/TextToSpeech/TextToSpeech.m +285 -0
- package/ios/TextToSpeech.xcodeproj/project.pbxproj +276 -0
- package/ios/TextToSpeech.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/TextToSpeech.xcodeproj/project.xcworkspace/xcuserdata/anton.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/TextToSpeech.xcodeproj/xcuserdata/anton.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +5 -0
- package/ios/TextToSpeech.xcodeproj/xcuserdata/anton.xcuserdatad/xcschemes/TextToSpeech.xcscheme +80 -0
- package/ios/TextToSpeech.xcodeproj/xcuserdata/anton.xcuserdatad/xcschemes/xcschememanagement.plist +22 -0
- package/package.json +27 -0
- package/windows/README.md +25 -0
- package/windows/RNTTS/PropertySheet.props +16 -0
- package/windows/RNTTS/RNTTS.cpp +224 -0
- package/windows/RNTTS/RNTTS.def +3 -0
- package/windows/RNTTS/RNTTS.h +75 -0
- package/windows/RNTTS/RNTTS.vcxproj +163 -0
- package/windows/RNTTS/RNTTS.vcxproj.filters +33 -0
- package/windows/RNTTS/ReactPackageProvider.cpp +15 -0
- package/windows/RNTTS/ReactPackageProvider.h +16 -0
- package/windows/RNTTS/ReactPackageProvider.idl +9 -0
- package/windows/RNTTS/packages.config +4 -0
- package/windows/RNTTS/pch.cpp +1 -0
- package/windows/RNTTS/pch.h +11 -0
- package/windows/RNTTS62.sln +254 -0
- package/windows/RNTTS63.sln +226 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
package net.no_mad.tts;
|
|
2
|
+
|
|
3
|
+
import android.media.AudioManager;
|
|
4
|
+
import android.os.Build;
|
|
5
|
+
import android.os.Bundle;
|
|
6
|
+
import android.content.Intent;
|
|
7
|
+
import android.content.ActivityNotFoundException;
|
|
8
|
+
import android.app.Activity;
|
|
9
|
+
import android.net.Uri;
|
|
10
|
+
import android.speech.tts.TextToSpeech;
|
|
11
|
+
import android.speech.tts.UtteranceProgressListener;
|
|
12
|
+
import android.speech.tts.Voice;
|
|
13
|
+
import android.content.pm.PackageInfo;
|
|
14
|
+
import android.content.pm.PackageManager;
|
|
15
|
+
import android.content.pm.PackageManager.NameNotFoundException;
|
|
16
|
+
import com.facebook.react.bridge.*;
|
|
17
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
18
|
+
|
|
19
|
+
import java.util.ArrayList;
|
|
20
|
+
import java.util.HashMap;
|
|
21
|
+
import java.util.Locale;
|
|
22
|
+
import java.util.Map;
|
|
23
|
+
|
|
24
|
+
public class TextToSpeechModule extends ReactContextBaseJavaModule {
|
|
25
|
+
|
|
26
|
+
private TextToSpeech tts;
|
|
27
|
+
private Boolean ready = null;
|
|
28
|
+
private ArrayList<Promise> initStatusPromises;
|
|
29
|
+
|
|
30
|
+
private boolean ducking = false;
|
|
31
|
+
private AudioManager audioManager;
|
|
32
|
+
private AudioManager.OnAudioFocusChangeListener afChangeListener;
|
|
33
|
+
|
|
34
|
+
private Map<String, Locale> localeCountryMap;
|
|
35
|
+
private Map<String, Locale> localeLanguageMap;
|
|
36
|
+
|
|
37
|
+
public TextToSpeechModule(ReactApplicationContext reactContext) {
|
|
38
|
+
super(reactContext);
|
|
39
|
+
audioManager = (AudioManager) reactContext.getApplicationContext().getSystemService(reactContext.AUDIO_SERVICE);
|
|
40
|
+
initStatusPromises = new ArrayList<Promise>();
|
|
41
|
+
//initialize ISO3, ISO2 languague country code mapping.
|
|
42
|
+
initCountryLanguageCodeMapping();
|
|
43
|
+
|
|
44
|
+
tts = new TextToSpeech(getReactApplicationContext(), new TextToSpeech.OnInitListener() {
|
|
45
|
+
@Override
|
|
46
|
+
public void onInit(int status) {
|
|
47
|
+
synchronized(initStatusPromises) {
|
|
48
|
+
ready = (status == TextToSpeech.SUCCESS) ? Boolean.TRUE : Boolean.FALSE;
|
|
49
|
+
for(Promise p: initStatusPromises) {
|
|
50
|
+
resolveReadyPromise(p);
|
|
51
|
+
}
|
|
52
|
+
initStatusPromises.clear();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
setUtteranceProgress();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private void setUtteranceProgress() {
|
|
61
|
+
if(tts != null)
|
|
62
|
+
{
|
|
63
|
+
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
|
|
64
|
+
@Override
|
|
65
|
+
public void onStart(String utteranceId) {
|
|
66
|
+
sendEvent("tts-start", utteranceId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Override
|
|
70
|
+
public void onDone(String utteranceId) {
|
|
71
|
+
if(ducking) {
|
|
72
|
+
audioManager.abandonAudioFocus(afChangeListener);
|
|
73
|
+
}
|
|
74
|
+
sendEvent("tts-finish", utteranceId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Override
|
|
78
|
+
public void onError(String utteranceId) {
|
|
79
|
+
if(ducking) {
|
|
80
|
+
audioManager.abandonAudioFocus(afChangeListener);
|
|
81
|
+
}
|
|
82
|
+
sendEvent("tts-error", utteranceId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Override
|
|
86
|
+
public void onStop(String utteranceId, boolean interrupted) {
|
|
87
|
+
if(ducking) {
|
|
88
|
+
audioManager.abandonAudioFocus(afChangeListener);
|
|
89
|
+
}
|
|
90
|
+
sendEvent("tts-cancel", utteranceId);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Override
|
|
94
|
+
public void onRangeStart (String utteranceId, int start, int end, int frame) {
|
|
95
|
+
WritableMap params = Arguments.createMap();
|
|
96
|
+
params.putString("utteranceId", utteranceId);
|
|
97
|
+
params.putInt("start", start);
|
|
98
|
+
params.putInt("end", end);
|
|
99
|
+
params.putInt("frame", frame);
|
|
100
|
+
sendEvent("tts-progress", params);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private void initCountryLanguageCodeMapping() {
|
|
107
|
+
String[] countries = Locale.getISOCountries();
|
|
108
|
+
localeCountryMap = new HashMap<String, Locale>(countries.length);
|
|
109
|
+
for (String country: countries) {
|
|
110
|
+
Locale locale = new Locale("", country);
|
|
111
|
+
localeCountryMap.put(locale.getISO3Country().toUpperCase(), locale);
|
|
112
|
+
}
|
|
113
|
+
String[] languages = Locale.getISOLanguages();
|
|
114
|
+
localeLanguageMap = new HashMap<String, Locale>(languages.length);
|
|
115
|
+
for (String language: languages) {
|
|
116
|
+
Locale locale = new Locale(language);
|
|
117
|
+
localeLanguageMap.put(locale.getISO3Language(), locale);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private String iso3CountryCodeToIso2CountryCode(String iso3CountryCode) {
|
|
122
|
+
return localeCountryMap.get(iso3CountryCode).getCountry();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private String iso3LanguageCodeToIso2LanguageCode(String iso3LanguageCode) {
|
|
126
|
+
return localeLanguageMap.get(iso3LanguageCode).getLanguage();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private void resolveReadyPromise(Promise promise) {
|
|
130
|
+
if (ready == Boolean.TRUE) {
|
|
131
|
+
promise.resolve("success");
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
promise.reject("no_engine", "No TTS engine installed");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static void resolvePromiseWithStatusCode(int statusCode, Promise promise) {
|
|
139
|
+
switch (statusCode) {
|
|
140
|
+
case TextToSpeech.SUCCESS:
|
|
141
|
+
promise.resolve("success");
|
|
142
|
+
break;
|
|
143
|
+
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
|
|
144
|
+
promise.resolve("lang_country_available");
|
|
145
|
+
break;
|
|
146
|
+
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
|
|
147
|
+
promise.resolve("lang_country_var_available");
|
|
148
|
+
break;
|
|
149
|
+
case TextToSpeech.ERROR_INVALID_REQUEST:
|
|
150
|
+
promise.reject("invalid_request", "Failure caused by an invalid request");
|
|
151
|
+
break;
|
|
152
|
+
case TextToSpeech.ERROR_NETWORK:
|
|
153
|
+
promise.reject("network_error", "Failure caused by a network connectivity problems");
|
|
154
|
+
break;
|
|
155
|
+
case TextToSpeech.ERROR_NETWORK_TIMEOUT:
|
|
156
|
+
promise.reject("network_timeout", "Failure caused by network timeout.");
|
|
157
|
+
break;
|
|
158
|
+
case TextToSpeech.ERROR_NOT_INSTALLED_YET:
|
|
159
|
+
promise.reject("not_installed_yet", "Unfinished download of voice data");
|
|
160
|
+
break;
|
|
161
|
+
case TextToSpeech.ERROR_OUTPUT:
|
|
162
|
+
promise.reject("output_error", "Failure related to the output (audio device or a file)");
|
|
163
|
+
break;
|
|
164
|
+
case TextToSpeech.ERROR_SERVICE:
|
|
165
|
+
promise.reject("service_error", "Failure of a TTS service");
|
|
166
|
+
break;
|
|
167
|
+
case TextToSpeech.ERROR_SYNTHESIS:
|
|
168
|
+
promise.reject("synthesis_error", "Failure of a TTS engine to synthesize the given input");
|
|
169
|
+
break;
|
|
170
|
+
case TextToSpeech.LANG_MISSING_DATA:
|
|
171
|
+
promise.reject("lang_missing_data", "Language data is missing");
|
|
172
|
+
break;
|
|
173
|
+
case TextToSpeech.LANG_NOT_SUPPORTED:
|
|
174
|
+
promise.reject("lang_not_supported", "Language is not supported");
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
promise.reject("error", "Unknown error code: " + statusCode);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private boolean isPackageInstalled(String packageName) {
|
|
183
|
+
PackageManager pm = getReactApplicationContext().getPackageManager();
|
|
184
|
+
try {
|
|
185
|
+
PackageInfo pi = pm.getPackageInfo(packageName, 0);
|
|
186
|
+
return true;
|
|
187
|
+
} catch (NameNotFoundException e) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@Override
|
|
193
|
+
public String getName() {
|
|
194
|
+
return "TextToSpeech";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@ReactMethod
|
|
198
|
+
public void getInitStatus(Promise promise) {
|
|
199
|
+
synchronized(initStatusPromises) {
|
|
200
|
+
if(ready == null) {
|
|
201
|
+
initStatusPromises.add(promise);
|
|
202
|
+
} else {
|
|
203
|
+
resolveReadyPromise(promise);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@ReactMethod
|
|
209
|
+
public void speak(String utterance, ReadableMap params, Promise promise) {
|
|
210
|
+
if(notReady(promise)) return;
|
|
211
|
+
|
|
212
|
+
if(ducking) {
|
|
213
|
+
// Request audio focus for playback
|
|
214
|
+
int amResult = audioManager.requestAudioFocus(afChangeListener,
|
|
215
|
+
// Use the music stream.
|
|
216
|
+
AudioManager.STREAM_MUSIC,
|
|
217
|
+
// Request permanent focus.
|
|
218
|
+
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
|
219
|
+
|
|
220
|
+
if(amResult != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
|
221
|
+
promise.reject("Android AudioManager error, failed to request audio focus");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
String utteranceId = Integer.toString(utterance.hashCode());
|
|
227
|
+
|
|
228
|
+
int speakResult = speak(utterance, utteranceId, params);
|
|
229
|
+
if(speakResult == TextToSpeech.SUCCESS) {
|
|
230
|
+
promise.resolve(utteranceId);
|
|
231
|
+
} else {
|
|
232
|
+
resolvePromiseWithStatusCode(speakResult, promise);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@ReactMethod
|
|
237
|
+
public void setDefaultLanguage(String language, Promise promise) {
|
|
238
|
+
if(notReady(promise)) return;
|
|
239
|
+
|
|
240
|
+
Locale locale = null;
|
|
241
|
+
|
|
242
|
+
if(language.indexOf("-") != -1) {
|
|
243
|
+
String[] parts = language.split("-");
|
|
244
|
+
locale = new Locale(parts[0], parts[1]);
|
|
245
|
+
} else {
|
|
246
|
+
locale = new Locale(language);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
int result = tts.setLanguage(locale);
|
|
251
|
+
resolvePromiseWithStatusCode(result, promise);
|
|
252
|
+
} catch (Exception e) {
|
|
253
|
+
promise.reject("error", "Unknown error code");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@ReactMethod
|
|
258
|
+
public void setDucking(Boolean ducking, Promise promise) {
|
|
259
|
+
if(notReady(promise)) return;
|
|
260
|
+
this.ducking = ducking;
|
|
261
|
+
promise.resolve("success");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@ReactMethod
|
|
265
|
+
public void setDefaultRate(Float rate, Boolean skipTransform, Promise promise) {
|
|
266
|
+
if(notReady(promise)) return;
|
|
267
|
+
|
|
268
|
+
if(skipTransform) {
|
|
269
|
+
int result = tts.setSpeechRate(rate);
|
|
270
|
+
resolvePromiseWithStatusCode(result, promise);
|
|
271
|
+
} else {
|
|
272
|
+
// normalize android rate
|
|
273
|
+
// rate value will be in the range 0.0 to 1.0
|
|
274
|
+
// let's convert it to the range of values Android platform expects,
|
|
275
|
+
// where 1.0 is no change of rate and 2.0 is the twice faster rate
|
|
276
|
+
float androidRate = rate.floatValue() < 0.5f ?
|
|
277
|
+
rate.floatValue() * 2 : // linear fit {0, 0}, {0.25, 0.5}, {0.5, 1}
|
|
278
|
+
rate.floatValue() * 4 - 1; // linear fit {{0.5, 1}, {0.75, 2}, {1, 3}}
|
|
279
|
+
int result = tts.setSpeechRate(androidRate);
|
|
280
|
+
resolvePromiseWithStatusCode(result, promise);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@ReactMethod
|
|
285
|
+
public void setDefaultPitch(Float pitch, Promise promise) {
|
|
286
|
+
if(notReady(promise)) return;
|
|
287
|
+
int result = tts.setPitch(pitch);
|
|
288
|
+
resolvePromiseWithStatusCode(result, promise);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@ReactMethod
|
|
292
|
+
public void setDefaultVoice(String voiceId, Promise promise) {
|
|
293
|
+
if(notReady(promise)) return;
|
|
294
|
+
|
|
295
|
+
if (Build.VERSION.SDK_INT >= 21) {
|
|
296
|
+
try {
|
|
297
|
+
for(Voice voice: tts.getVoices()) {
|
|
298
|
+
if(voice.getName().equals(voiceId)) {
|
|
299
|
+
int result = tts.setVoice(voice);
|
|
300
|
+
resolvePromiseWithStatusCode(result, promise);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch (Exception e) {
|
|
305
|
+
// Purposefully ignore exceptions here due to some buggy TTS engines.
|
|
306
|
+
// See http://stackoverflow.com/questions/26730082/illegalargumentexception-invalid-int-os-with-samsung-tts
|
|
307
|
+
}
|
|
308
|
+
promise.reject("not_found", "The selected voice was not found");
|
|
309
|
+
} else {
|
|
310
|
+
promise.reject("not_available", "Android API 21 level or higher is required");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@ReactMethod
|
|
315
|
+
public void voices(Promise promise) {
|
|
316
|
+
if(notReady(promise)) return;
|
|
317
|
+
|
|
318
|
+
WritableArray voiceArray = Arguments.createArray();
|
|
319
|
+
|
|
320
|
+
if (Build.VERSION.SDK_INT >= 21) {
|
|
321
|
+
try {
|
|
322
|
+
for(Voice voice: tts.getVoices()) {
|
|
323
|
+
WritableMap voiceMap = Arguments.createMap();
|
|
324
|
+
voiceMap.putString("id", voice.getName());
|
|
325
|
+
voiceMap.putString("name", voice.getName());
|
|
326
|
+
|
|
327
|
+
String language = iso3LanguageCodeToIso2LanguageCode(voice.getLocale().getISO3Language());
|
|
328
|
+
String country = voice.getLocale().getISO3Country();
|
|
329
|
+
if(country != "") {
|
|
330
|
+
language += "-" + iso3CountryCodeToIso2CountryCode(country);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
voiceMap.putString("language", language);
|
|
334
|
+
voiceMap.putInt("quality", voice.getQuality());
|
|
335
|
+
voiceMap.putInt("latency", voice.getLatency());
|
|
336
|
+
voiceMap.putBoolean("networkConnectionRequired", voice.isNetworkConnectionRequired());
|
|
337
|
+
voiceMap.putBoolean("notInstalled", voice.getFeatures().contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED));
|
|
338
|
+
voiceArray.pushMap(voiceMap);
|
|
339
|
+
}
|
|
340
|
+
} catch (Exception e) {
|
|
341
|
+
// Purposefully ignore exceptions here due to some buggy TTS engines.
|
|
342
|
+
// See http://stackoverflow.com/questions/26730082/illegalargumentexception-invalid-int-os-with-samsung-tts
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
promise.resolve(voiceArray);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
@ReactMethod
|
|
350
|
+
public void setDefaultEngine(String engineName, final Promise promise) {
|
|
351
|
+
if(notReady(promise)) return;
|
|
352
|
+
|
|
353
|
+
if(isPackageInstalled(engineName)) {
|
|
354
|
+
ready = null;
|
|
355
|
+
onCatalystInstanceDestroy();
|
|
356
|
+
tts = new TextToSpeech(getReactApplicationContext(), new TextToSpeech.OnInitListener() {
|
|
357
|
+
@Override
|
|
358
|
+
public void onInit(int status) {
|
|
359
|
+
synchronized(initStatusPromises) {
|
|
360
|
+
ready = (status == TextToSpeech.SUCCESS) ? Boolean.TRUE : Boolean.FALSE;
|
|
361
|
+
for(Promise p: initStatusPromises) {
|
|
362
|
+
resolveReadyPromise(p);
|
|
363
|
+
}
|
|
364
|
+
initStatusPromises.clear();
|
|
365
|
+
promise.resolve(ready);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}, engineName);
|
|
369
|
+
|
|
370
|
+
setUtteranceProgress();
|
|
371
|
+
} else {
|
|
372
|
+
promise.reject("not_found", "The selected engine was not found");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@ReactMethod
|
|
377
|
+
public void engines(Promise promise) {
|
|
378
|
+
if(notReady(promise)) return;
|
|
379
|
+
|
|
380
|
+
WritableArray engineArray = Arguments.createArray();
|
|
381
|
+
|
|
382
|
+
if (Build.VERSION.SDK_INT >= 14) {
|
|
383
|
+
try {
|
|
384
|
+
String defaultEngineName = tts.getDefaultEngine();
|
|
385
|
+
for(TextToSpeech.EngineInfo engine: tts.getEngines()) {
|
|
386
|
+
WritableMap engineMap = Arguments.createMap();
|
|
387
|
+
|
|
388
|
+
engineMap.putString("name", engine.name);
|
|
389
|
+
engineMap.putString("label", engine.label);
|
|
390
|
+
engineMap.putBoolean("default", engine.name.equals(defaultEngineName));
|
|
391
|
+
engineMap.putInt("icon", engine.icon);
|
|
392
|
+
|
|
393
|
+
engineArray.pushMap(engineMap);
|
|
394
|
+
}
|
|
395
|
+
} catch (Exception e) {
|
|
396
|
+
promise.reject("error", "Unknown error code");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
promise.resolve(engineArray);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@ReactMethod
|
|
404
|
+
public void stop(Promise promise) {
|
|
405
|
+
if(notReady(promise)) return;
|
|
406
|
+
|
|
407
|
+
int result = tts.stop();
|
|
408
|
+
boolean resultValue = (result == TextToSpeech.SUCCESS) ? Boolean.TRUE : Boolean.FALSE;
|
|
409
|
+
promise.resolve(resultValue);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@ReactMethod
|
|
413
|
+
private void requestInstallEngine(Promise promise) {
|
|
414
|
+
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
415
|
+
intent.setData(Uri.parse("market://details?id=com.google.android.tts"));
|
|
416
|
+
try {
|
|
417
|
+
getCurrentActivity().startActivity(intent);
|
|
418
|
+
promise.resolve("success");
|
|
419
|
+
} catch (Exception e) {
|
|
420
|
+
promise.reject("error", "Could not open Google Text to Speech App in the Play Store");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
@ReactMethod
|
|
425
|
+
private void requestInstallData(Promise promise) {
|
|
426
|
+
Intent intent = new Intent();
|
|
427
|
+
intent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
|
|
428
|
+
try {
|
|
429
|
+
getCurrentActivity().startActivity(intent);
|
|
430
|
+
promise.resolve("success");
|
|
431
|
+
} catch (ActivityNotFoundException e) {
|
|
432
|
+
promise.reject("no_engine", "No TTS engine installed");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* called on React Native Reloading JavaScript
|
|
438
|
+
* https://stackoverflow.com/questions/15563361/tts-leaked-serviceconnection
|
|
439
|
+
*/
|
|
440
|
+
@Override
|
|
441
|
+
public void onCatalystInstanceDestroy() {
|
|
442
|
+
super.onCatalystInstanceDestroy();
|
|
443
|
+
if(tts != null) {
|
|
444
|
+
tts.stop();
|
|
445
|
+
tts.shutdown();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private boolean notReady(Promise promise) {
|
|
450
|
+
if(ready == null) {
|
|
451
|
+
promise.reject("not_ready", "TTS is not ready");
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
else if(ready != Boolean.TRUE) {
|
|
455
|
+
resolveReadyPromise(promise);
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@SuppressWarnings("deprecation")
|
|
462
|
+
private int speak(String utterance, String utteranceId, ReadableMap inputParams) {
|
|
463
|
+
String audioStreamTypeString = inputParams.hasKey("KEY_PARAM_STREAM") ? inputParams.getString("KEY_PARAM_STREAM") : "";
|
|
464
|
+
float volume = inputParams.hasKey("KEY_PARAM_VOLUME") ? (float) inputParams.getDouble("KEY_PARAM_VOLUME") : 1.0f;
|
|
465
|
+
float pan = inputParams.hasKey("KEY_PARAM_PAN") ? (float) inputParams.getDouble("KEY_PARAM_PAN") : 0.0f;
|
|
466
|
+
|
|
467
|
+
int audioStreamType;
|
|
468
|
+
switch(audioStreamTypeString) {
|
|
469
|
+
/*
|
|
470
|
+
// This has been added in API level 26, commenting out for now
|
|
471
|
+
|
|
472
|
+
case "STREAM_ACCESSIBILITY":
|
|
473
|
+
audioStreamType = AudioManager.STREAM_ACCESSIBILITY;
|
|
474
|
+
break;
|
|
475
|
+
*/
|
|
476
|
+
case "STREAM_ALARM":
|
|
477
|
+
audioStreamType = AudioManager.STREAM_ALARM;
|
|
478
|
+
break;
|
|
479
|
+
case "STREAM_DTMF":
|
|
480
|
+
audioStreamType = AudioManager.STREAM_DTMF;
|
|
481
|
+
break;
|
|
482
|
+
case "STREAM_MUSIC":
|
|
483
|
+
audioStreamType = AudioManager.STREAM_MUSIC;
|
|
484
|
+
break;
|
|
485
|
+
case "STREAM_NOTIFICATION":
|
|
486
|
+
audioStreamType = AudioManager.STREAM_NOTIFICATION;
|
|
487
|
+
break;
|
|
488
|
+
case "STREAM_RING":
|
|
489
|
+
audioStreamType = AudioManager.STREAM_RING;
|
|
490
|
+
break;
|
|
491
|
+
case "STREAM_SYSTEM":
|
|
492
|
+
audioStreamType = AudioManager.STREAM_SYSTEM;
|
|
493
|
+
break;
|
|
494
|
+
case "STREAM_VOICE_CALL":
|
|
495
|
+
audioStreamType = AudioManager.STREAM_VOICE_CALL;
|
|
496
|
+
break;
|
|
497
|
+
default:
|
|
498
|
+
audioStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (Build.VERSION.SDK_INT >= 21) {
|
|
502
|
+
Bundle params = new Bundle();
|
|
503
|
+
params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, audioStreamType);
|
|
504
|
+
params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume);
|
|
505
|
+
params.putFloat(TextToSpeech.Engine.KEY_PARAM_PAN, pan);
|
|
506
|
+
return tts.speak(utterance, TextToSpeech.QUEUE_ADD, params, utteranceId);
|
|
507
|
+
} else {
|
|
508
|
+
HashMap<String, String> params = new HashMap();
|
|
509
|
+
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
|
|
510
|
+
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(audioStreamType));
|
|
511
|
+
params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, String.valueOf(volume));
|
|
512
|
+
params.put(TextToSpeech.Engine.KEY_PARAM_PAN, String.valueOf(pan));
|
|
513
|
+
return tts.speak(utterance, TextToSpeech.QUEUE_ADD, params);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private void sendEvent(String eventName, String utteranceId) {
|
|
518
|
+
WritableMap params = Arguments.createMap();
|
|
519
|
+
params.putString("utteranceId", utteranceId);
|
|
520
|
+
sendEvent(eventName, params);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private void sendEvent(String eventName, WritableMap params) {
|
|
524
|
+
getReactApplicationContext()
|
|
525
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
526
|
+
.emit(eventName, params);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
@ReactMethod
|
|
530
|
+
public void removeListeners(Integer count) {
|
|
531
|
+
// Keep: Required for RN built in Event Emitter Calls.
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
@ReactMethod
|
|
535
|
+
public void addListener(String eventName) {
|
|
536
|
+
// Keep: Required for RN built in Event Emitter Calls.
|
|
537
|
+
}
|
|
538
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package net.no_mad.tts;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage;
|
|
4
|
+
import com.facebook.react.bridge.JavaScriptModule;
|
|
5
|
+
import com.facebook.react.bridge.NativeModule;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
8
|
+
|
|
9
|
+
import java.util.ArrayList;
|
|
10
|
+
import java.util.Collections;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
|
|
13
|
+
public class TextToSpeechPackage implements ReactPackage {
|
|
14
|
+
|
|
15
|
+
@Override
|
|
16
|
+
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
|
17
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
18
|
+
modules.add(new TextToSpeechModule(reactContext));
|
|
19
|
+
return modules;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Deprecated in RN 0.47
|
|
23
|
+
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
|
24
|
+
return Collections.emptyList();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Override
|
|
28
|
+
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
29
|
+
return Collections.emptyList();
|
|
30
|
+
}
|
|
31
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as RN from "react-native";
|
|
2
|
+
|
|
3
|
+
type SimpleEvents = "tts-start" | "tts-finish" | "tts-error" | "tts-cancel";
|
|
4
|
+
type SimpleEvent = {
|
|
5
|
+
utteranceId: string | number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type ProgressEventName = "tts-progress";
|
|
9
|
+
type ProgressEvent = {
|
|
10
|
+
utteranceId: string | number;
|
|
11
|
+
location: number;
|
|
12
|
+
length: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TtsEvents = SimpleEvents | ProgressEventName;
|
|
16
|
+
export type TtsEvent<
|
|
17
|
+
T extends TtsEvents = TtsEvents
|
|
18
|
+
> = T extends ProgressEventName ? ProgressEvent : SimpleEvent;
|
|
19
|
+
export type TtsEventHandler<T extends TtsEvents = TtsEvents> = (
|
|
20
|
+
event: TtsEvent<T>
|
|
21
|
+
) => any;
|
|
22
|
+
|
|
23
|
+
export type TtsError = {
|
|
24
|
+
code:
|
|
25
|
+
| "no_engine"
|
|
26
|
+
| "error"
|
|
27
|
+
| "not_ready"
|
|
28
|
+
| "invalid_request"
|
|
29
|
+
| "network_error"
|
|
30
|
+
| "network_timeout"
|
|
31
|
+
| "not_installed_yet"
|
|
32
|
+
| "output_error"
|
|
33
|
+
| "service_error"
|
|
34
|
+
| "synthesis_error"
|
|
35
|
+
| "lang_missing_data"
|
|
36
|
+
| "lang_not_supported"
|
|
37
|
+
| "Android AudioManager error"
|
|
38
|
+
| "not_available"
|
|
39
|
+
| "not_found"
|
|
40
|
+
| "bad_rate";
|
|
41
|
+
message: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type IOSSilentSwitchBehavior = "inherit" | "ignore" | "obey"
|
|
45
|
+
|
|
46
|
+
export type Voice = {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
language: string;
|
|
50
|
+
quality: number;
|
|
51
|
+
latency: number;
|
|
52
|
+
networkConnectionRequired: boolean;
|
|
53
|
+
notInstalled: boolean;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type Engine = {
|
|
57
|
+
name: string;
|
|
58
|
+
label: string;
|
|
59
|
+
default: boolean;
|
|
60
|
+
icon: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type AndroidOptions = {
|
|
64
|
+
/** Parameter key to specify the audio stream type to be used when speaking text or playing back a file */
|
|
65
|
+
KEY_PARAM_STREAM:
|
|
66
|
+
| "STREAM_VOICE_CALL"
|
|
67
|
+
| "STREAM_SYSTEM"
|
|
68
|
+
| "STREAM_RING"
|
|
69
|
+
| "STREAM_MUSIC"
|
|
70
|
+
| "STREAM_ALARM"
|
|
71
|
+
| "STREAM_NOTIFICATION"
|
|
72
|
+
| "STREAM_DTMF"
|
|
73
|
+
| "STREAM_ACCESSIBILITY";
|
|
74
|
+
/** Parameter key to specify the speech volume relative to the current stream type volume used when speaking text. Volume is specified as a float ranging from 0 to 1 where 0 is silence, and 1 is the maximum volume (the default behavior). */
|
|
75
|
+
KEY_PARAM_VOLUME: number;
|
|
76
|
+
/** Parameter key to specify how the speech is panned from left to right when speaking text. Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan, 0 to center (the default behavior), and +1 to hard-right. */
|
|
77
|
+
KEY_PARAM_PAN: number;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type Options =
|
|
81
|
+
| string
|
|
82
|
+
| {
|
|
83
|
+
iosVoiceId: string;
|
|
84
|
+
rate: number;
|
|
85
|
+
androidParams: AndroidOptions;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export class ReactNativeTts extends RN.NativeEventEmitter {
|
|
89
|
+
getInitStatus: () => Promise<"success">;
|
|
90
|
+
requestInstallEngine: () => Promise<"success">;
|
|
91
|
+
requestInstallData: () => Promise<"success">;
|
|
92
|
+
setDucking: (enabled: boolean) => Promise<"success">;
|
|
93
|
+
setDefaultEngine: (engineName: string) => Promise<boolean>;
|
|
94
|
+
setDefaultVoice: (voiceId: string) => Promise<"success">;
|
|
95
|
+
setDefaultRate: (rate: number, skipTransform?: boolean) => Promise<"success">;
|
|
96
|
+
setDefaultPitch: (pitch: number) => Promise<"success">;
|
|
97
|
+
setDefaultLanguage: (language: string) => Promise<"success">;
|
|
98
|
+
setIgnoreSilentSwitch: (ignoreSilentSwitch: IOSSilentSwitchBehavior) => Promise<boolean>;
|
|
99
|
+
voices: () => Promise<Voice[]>;
|
|
100
|
+
engines: () => Promise<Engine[]>;
|
|
101
|
+
/** Read the sentence and return an id for the task. */
|
|
102
|
+
speak: (utterance: string, options?: Options) => string | number;
|
|
103
|
+
stop: (onWordBoundary?: boolean) => Promise<boolean>;
|
|
104
|
+
pause: (onWordBoundary?: boolean) => Promise<boolean>;
|
|
105
|
+
resume: () => Promise<boolean>;
|
|
106
|
+
addEventListener: <T extends TtsEvents>(
|
|
107
|
+
type: T,
|
|
108
|
+
handler: TtsEventHandler<T>
|
|
109
|
+
) => void;
|
|
110
|
+
removeEventListener: <T extends TtsEvents>(
|
|
111
|
+
type: T,
|
|
112
|
+
handler: TtsEventHandler<T>
|
|
113
|
+
) => void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
declare const Tts: ReactNativeTts;
|
|
117
|
+
|
|
118
|
+
export default Tts;
|