@capgo/capacitor-speech-synthesis 8.0.4 → 8.0.6
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.
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package ee.forgr.plugin.speechsynthesis;
|
|
2
2
|
|
|
3
|
-
import android.os.Build;
|
|
4
3
|
import android.os.Bundle;
|
|
5
4
|
import android.speech.tts.TextToSpeech;
|
|
6
5
|
import android.speech.tts.UtteranceProgressListener;
|
|
@@ -13,7 +12,6 @@ import com.getcapacitor.PluginMethod;
|
|
|
13
12
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
14
13
|
import java.io.File;
|
|
15
14
|
import java.util.ArrayList;
|
|
16
|
-
import java.util.HashMap;
|
|
17
15
|
import java.util.HashSet;
|
|
18
16
|
import java.util.List;
|
|
19
17
|
import java.util.Locale;
|
|
@@ -23,7 +21,7 @@ import org.json.JSONException;
|
|
|
23
21
|
@CapacitorPlugin(name = "SpeechSynthesis")
|
|
24
22
|
public class SpeechSynthesisPlugin extends Plugin {
|
|
25
23
|
|
|
26
|
-
private final String pluginVersion = "8.0.
|
|
24
|
+
private final String pluginVersion = "8.0.6";
|
|
27
25
|
private TextToSpeech tts;
|
|
28
26
|
private int utteranceIdCounter = 0;
|
|
29
27
|
private boolean ttsInitialized = false;
|
|
@@ -129,7 +127,7 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
129
127
|
String language = call.getString("language");
|
|
130
128
|
String voiceId = call.getString("voiceId");
|
|
131
129
|
|
|
132
|
-
if (
|
|
130
|
+
if (voiceId != null) {
|
|
133
131
|
Voice voice = findVoiceById(voiceId);
|
|
134
132
|
if (voice != null) {
|
|
135
133
|
tts.setVoice(voice);
|
|
@@ -159,16 +157,7 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
159
157
|
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
|
|
160
158
|
|
|
161
159
|
// Speak
|
|
162
|
-
|
|
163
|
-
tts.speak(text, queueMode, params, utteranceId);
|
|
164
|
-
} else {
|
|
165
|
-
HashMap<String, String> paramsMap = new HashMap<>();
|
|
166
|
-
paramsMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
|
|
167
|
-
if (volume != null) {
|
|
168
|
-
paramsMap.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, String.valueOf(volume));
|
|
169
|
-
}
|
|
170
|
-
tts.speak(text, queueMode, paramsMap);
|
|
171
|
-
}
|
|
160
|
+
tts.speak(text, queueMode, params, utteranceId);
|
|
172
161
|
|
|
173
162
|
JSObject result = new JSObject();
|
|
174
163
|
result.put("utteranceId", utteranceId);
|
|
@@ -194,7 +183,7 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
194
183
|
String language = call.getString("language");
|
|
195
184
|
String voiceId = call.getString("voiceId");
|
|
196
185
|
|
|
197
|
-
if (
|
|
186
|
+
if (voiceId != null) {
|
|
198
187
|
Voice voice = findVoiceById(voiceId);
|
|
199
188
|
if (voice != null) {
|
|
200
189
|
tts.setVoice(voice);
|
|
@@ -215,34 +204,18 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
215
204
|
File outputFile = new File(getContext().getFilesDir(), utteranceId + ".wav");
|
|
216
205
|
|
|
217
206
|
// Synthesize to file
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
call.resolve(response);
|
|
229
|
-
} else {
|
|
230
|
-
call.reject("Failed to synthesize to file");
|
|
231
|
-
}
|
|
207
|
+
Bundle params = new Bundle();
|
|
208
|
+
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
|
|
209
|
+
|
|
210
|
+
int result = tts.synthesizeToFile(text, params, outputFile, utteranceId);
|
|
211
|
+
|
|
212
|
+
if (result == TextToSpeech.SUCCESS) {
|
|
213
|
+
JSObject response = new JSObject();
|
|
214
|
+
response.put("filePath", outputFile.getAbsolutePath());
|
|
215
|
+
response.put("utteranceId", utteranceId);
|
|
216
|
+
call.resolve(response);
|
|
232
217
|
} else {
|
|
233
|
-
|
|
234
|
-
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
|
|
235
|
-
|
|
236
|
-
int result = tts.synthesizeToFile(text, params, outputFile.getAbsolutePath());
|
|
237
|
-
|
|
238
|
-
if (result == TextToSpeech.SUCCESS) {
|
|
239
|
-
JSObject response = new JSObject();
|
|
240
|
-
response.put("filePath", outputFile.getAbsolutePath());
|
|
241
|
-
response.put("utteranceId", utteranceId);
|
|
242
|
-
call.resolve(response);
|
|
243
|
-
} else {
|
|
244
|
-
call.reject("Failed to synthesize to file");
|
|
245
|
-
}
|
|
218
|
+
call.reject("Failed to synthesize to file");
|
|
246
219
|
}
|
|
247
220
|
}
|
|
248
221
|
|
|
@@ -289,7 +262,7 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
289
262
|
public void getVoices(PluginCall call) {
|
|
290
263
|
JSArray voicesArray = new JSArray();
|
|
291
264
|
|
|
292
|
-
if (
|
|
265
|
+
if (tts != null) {
|
|
293
266
|
Set<Voice> voices = tts.getVoices();
|
|
294
267
|
if (voices != null) {
|
|
295
268
|
for (Voice voice : voices) {
|
|
@@ -299,19 +272,6 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
299
272
|
voiceInfo.put("language", voice.getLocale().toLanguageTag());
|
|
300
273
|
voiceInfo.put("isNetworkConnectionRequired", voice.isNetworkConnectionRequired());
|
|
301
274
|
|
|
302
|
-
voicesArray.put(voiceInfo);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
} else {
|
|
306
|
-
// Fallback for older Android versions
|
|
307
|
-
Locale[] locales = Locale.getAvailableLocales();
|
|
308
|
-
for (Locale locale : locales) {
|
|
309
|
-
if (tts != null && tts.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE) {
|
|
310
|
-
JSObject voiceInfo = new JSObject();
|
|
311
|
-
String tag = locale.toLanguageTag();
|
|
312
|
-
voiceInfo.put("id", tag);
|
|
313
|
-
voiceInfo.put("name", locale.getDisplayName());
|
|
314
|
-
voiceInfo.put("language", tag);
|
|
315
275
|
voicesArray.put(voiceInfo);
|
|
316
276
|
}
|
|
317
277
|
}
|
|
@@ -326,21 +286,13 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
326
286
|
public void getLanguages(PluginCall call) {
|
|
327
287
|
Set<String> languageSet = new HashSet<>();
|
|
328
288
|
|
|
329
|
-
if (
|
|
289
|
+
if (tts != null) {
|
|
330
290
|
Set<Voice> voices = tts.getVoices();
|
|
331
291
|
if (voices != null) {
|
|
332
292
|
for (Voice voice : voices) {
|
|
333
293
|
languageSet.add(voice.getLocale().toLanguageTag());
|
|
334
294
|
}
|
|
335
295
|
}
|
|
336
|
-
} else {
|
|
337
|
-
// Fallback for older Android versions
|
|
338
|
-
Locale[] locales = Locale.getAvailableLocales();
|
|
339
|
-
for (Locale locale : locales) {
|
|
340
|
-
if (tts != null && tts.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE) {
|
|
341
|
-
languageSet.add(locale.toLanguageTag());
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
296
|
}
|
|
345
297
|
|
|
346
298
|
List<String> languages = new ArrayList<>(languageSet);
|
|
@@ -375,11 +327,7 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
375
327
|
return;
|
|
376
328
|
}
|
|
377
329
|
|
|
378
|
-
boolean isAvailable =
|
|
379
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
380
|
-
Voice voice = findVoiceById(voiceId);
|
|
381
|
-
isAvailable = voice != null;
|
|
382
|
-
}
|
|
330
|
+
boolean isAvailable = findVoiceById(voiceId) != null;
|
|
383
331
|
|
|
384
332
|
JSObject result = new JSObject();
|
|
385
333
|
result.put("isAvailable", isAvailable);
|
|
@@ -414,7 +362,7 @@ public class SpeechSynthesisPlugin extends Plugin {
|
|
|
414
362
|
}
|
|
415
363
|
|
|
416
364
|
private Voice findVoiceById(String voiceId) {
|
|
417
|
-
if (
|
|
365
|
+
if (tts != null) {
|
|
418
366
|
Set<Voice> voices = tts.getVoices();
|
|
419
367
|
if (voices != null) {
|
|
420
368
|
for (Voice voice : voices) {
|
|
@@ -7,7 +7,7 @@ import AVFoundation
|
|
|
7
7
|
*/
|
|
8
8
|
@objc(SpeechSynthesisPlugin)
|
|
9
9
|
public class SpeechSynthesisPlugin: CAPPlugin, CAPBridgedPlugin, AVSpeechSynthesizerDelegate {
|
|
10
|
-
private let pluginVersion: String = "8.0.
|
|
10
|
+
private let pluginVersion: String = "8.0.6"
|
|
11
11
|
public let identifier = "SpeechSynthesisPlugin"
|
|
12
12
|
public let jsName = "SpeechSynthesis"
|
|
13
13
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -122,32 +122,28 @@ public class SpeechSynthesisPlugin: CAPPlugin, CAPBridgedPlugin, AVSpeechSynthes
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// Write to file
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
125
|
+
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
126
|
+
let audioFilename = documentsPath.appendingPathComponent("\(utteranceId).caf")
|
|
127
|
+
|
|
128
|
+
synthesizer?.write(utterance) { (buffer: AVAudioBuffer) in
|
|
129
|
+
guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
|
|
130
|
+
call.reject("Failed to get PCM buffer")
|
|
131
|
+
return
|
|
132
|
+
}
|
|
134
133
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
call.reject("Failed to create audio file")
|
|
134
|
+
if let audioFile = try? AVAudioFile(forWriting: audioFilename, settings: pcmBuffer.format.settings) {
|
|
135
|
+
do {
|
|
136
|
+
try audioFile.write(from: pcmBuffer)
|
|
137
|
+
call.resolve([
|
|
138
|
+
"filePath": audioFilename.path,
|
|
139
|
+
"utteranceId": utteranceId
|
|
140
|
+
])
|
|
141
|
+
} catch {
|
|
142
|
+
call.reject("Failed to write audio file: \(error.localizedDescription)")
|
|
147
143
|
}
|
|
144
|
+
} else {
|
|
145
|
+
call.reject("Failed to create audio file")
|
|
148
146
|
}
|
|
149
|
-
} else {
|
|
150
|
-
call.reject("synthesizeToFile requires iOS 13.0 or later")
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
149
|
|
|
@@ -189,24 +185,20 @@ public class SpeechSynthesisPlugin: CAPPlugin, CAPBridgedPlugin, AVSpeechSynthes
|
|
|
189
185
|
"language": voice.language
|
|
190
186
|
]
|
|
191
187
|
|
|
192
|
-
// Add gender
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
info["gender"] = "neutral"
|
|
201
|
-
}
|
|
188
|
+
// Add gender
|
|
189
|
+
switch voice.gender {
|
|
190
|
+
case .male:
|
|
191
|
+
info["gender"] = "male"
|
|
192
|
+
case .female:
|
|
193
|
+
info["gender"] = "female"
|
|
194
|
+
default:
|
|
195
|
+
info["gender"] = "neutral"
|
|
202
196
|
}
|
|
203
197
|
|
|
204
198
|
// Add network requirement
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
info["isNetworkConnectionRequired"] = voice.quality.rawValue < 2
|
|
209
|
-
}
|
|
199
|
+
// Higher quality voices (enhanced/premium) typically don't require network
|
|
200
|
+
// Default quality = 1, Enhanced quality = 2
|
|
201
|
+
info["isNetworkConnectionRequired"] = voice.quality.rawValue < 2
|
|
210
202
|
|
|
211
203
|
return info
|
|
212
204
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-speech-synthesis",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.6",
|
|
4
4
|
"description": "Synthesize speech from text with full control over language, voice, pitch, rate, and volume.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|