react-native-davoice-tts 1.0.232 → 1.0.234
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/TTSRNBridge.podspec
CHANGED
|
@@ -2,7 +2,7 @@ require 'json'
|
|
|
2
2
|
|
|
3
3
|
Pod::Spec.new do |s|
|
|
4
4
|
s.name = "TTSRNBridge"
|
|
5
|
-
s.version = "1.0.
|
|
5
|
+
s.version = "1.0.107" # Update to your package version
|
|
6
6
|
s.summary = "TTS for React Native."
|
|
7
7
|
s.description = <<-DESC
|
|
8
8
|
A React Native module for tts .
|
|
@@ -17,6 +17,12 @@ import java.nio.ByteBuffer;
|
|
|
17
17
|
import java.nio.ByteOrder;
|
|
18
18
|
import java.util.Locale;
|
|
19
19
|
import android.util.Base64;
|
|
20
|
+
import android.media.MediaPlayer;
|
|
21
|
+
|
|
22
|
+
import android.util.Log;
|
|
23
|
+
import android.content.res.AssetFileDescriptor;
|
|
24
|
+
import java.io.IOException;
|
|
25
|
+
|
|
20
26
|
|
|
21
27
|
import com.davoice.tts.DaVoiceTTSInterface;
|
|
22
28
|
|
|
@@ -24,6 +30,7 @@ public class DaVoiceTTSBridge extends ReactContextBaseJavaModule {
|
|
|
24
30
|
|
|
25
31
|
private final DaVoiceTTSInterface tts;
|
|
26
32
|
private final ReactApplicationContext reactCtx;
|
|
33
|
+
final String TAG = "TTS";
|
|
27
34
|
|
|
28
35
|
public DaVoiceTTSBridge(ReactApplicationContext context) {
|
|
29
36
|
super(context);
|
|
@@ -198,36 +205,127 @@ public class DaVoiceTTSBridge extends ReactContextBaseJavaModule {
|
|
|
198
205
|
dst[f] = acc / channels;
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
|
-
|
|
202
|
-
@ReactMethod
|
|
203
|
-
public void playWav(String pathOrURL, boolean markAsLast, Promise promise) {
|
|
204
|
-
|
|
208
|
+
|
|
209
|
+
// @ReactMethod
|
|
210
|
+
// public void playWav(String pathOrURL, boolean markAsLast, Promise promise) {
|
|
211
|
+
// Log.d(TAG, "playWav() called with: " + pathOrURL + " | markAsLast=" + markAsLast);
|
|
212
|
+
// try {
|
|
213
|
+
// if (isHttpOrHttps(pathOrURL)) {
|
|
214
|
+
// promise.reject("unsupported_url", "Remote URLs not supported. Download to a local file first.");
|
|
215
|
+
// return;
|
|
216
|
+
// }
|
|
217
|
+
|
|
218
|
+
// // --- NEW: handle require() assets from RN ---
|
|
219
|
+
// if (pathOrURL.startsWith("asset:/")) {
|
|
220
|
+
// Log.e(TAG, "Rejected remote URL: " + pathOrURL);
|
|
221
|
+
// String assetName = pathOrURL.replace("asset:/", "");
|
|
222
|
+
// Log.d(TAG, "Detected bundled asset: " + assetName);
|
|
223
|
+
// try (AssetFileDescriptor afd = getReactApplicationContext().getAssets().openFd(assetName)) {
|
|
224
|
+
// tts.playWav(afd, markAsLast); // overload that accepts AssetFileDescriptor
|
|
225
|
+
// promise.resolve("queued");
|
|
226
|
+
// return;
|
|
227
|
+
// } catch (IOException e) {
|
|
228
|
+
// promise.reject("asset_load_failed", "Unable to open bundled asset: " + e.getMessage(), e);
|
|
229
|
+
// return;
|
|
230
|
+
// }
|
|
231
|
+
// }
|
|
232
|
+
|
|
233
|
+
// String path = pathOrURL;
|
|
234
|
+
// if (isFileUrl(pathOrURL)) {
|
|
235
|
+
// Uri u = Uri.parse(pathOrURL);
|
|
236
|
+
// File f = new File(u.getPath());
|
|
237
|
+
// path = f.getAbsolutePath();
|
|
238
|
+
// }
|
|
239
|
+
// if (path == null) {
|
|
240
|
+
// promise.reject("bad_path", "Invalid file URL");
|
|
241
|
+
// return;
|
|
242
|
+
// }
|
|
243
|
+
// File f = new File(path);
|
|
244
|
+
// if (!f.exists()) {
|
|
245
|
+
// promise.reject("file_missing", "WAV file does not exist at path");
|
|
246
|
+
// return;
|
|
247
|
+
// }
|
|
248
|
+
// tts.playWav(f, markAsLast);
|
|
249
|
+
// promise.resolve("queued");
|
|
250
|
+
// } catch (Exception e) {
|
|
251
|
+
// promise.reject("PlayWavError", e.getMessage(), e);
|
|
252
|
+
// }
|
|
253
|
+
// }
|
|
254
|
+
// ADD
|
|
255
|
+
|
|
256
|
+
@ReactMethod
|
|
257
|
+
public void playWav(String pathOrURL, boolean markAsLast, Promise promise) {
|
|
258
|
+
final String TAG = "TTS";
|
|
259
|
+
Log.d(TAG, "playWav() called with: " + pathOrURL + " | markAsLast=" + markAsLast);
|
|
260
|
+
// 1️⃣ Handle RN dev server URLs (http://localhost:8081/...) by downloading them to cache first
|
|
261
|
+
try {
|
|
205
262
|
if (isHttpOrHttps(pathOrURL)) {
|
|
206
|
-
|
|
263
|
+
Log.w(TAG, "Downloading asset from dev server: " + pathOrURL);
|
|
264
|
+
java.net.URL url = new java.net.URL(pathOrURL);
|
|
265
|
+
java.io.InputStream in = url.openStream();
|
|
266
|
+
File out = new File(reactCtx.getCacheDir(), "rn_asset_" + System.currentTimeMillis() + ".wav");
|
|
267
|
+
try (FileOutputStream fos = new FileOutputStream(out)) {
|
|
268
|
+
byte[] buf = new byte[8192];
|
|
269
|
+
int len;
|
|
270
|
+
while ((len = in.read(buf)) != -1) fos.write(buf, 0, len);
|
|
271
|
+
}
|
|
272
|
+
in.close();
|
|
273
|
+
Log.d(TAG, "Downloaded asset to: " + out.getAbsolutePath());
|
|
274
|
+
tts.playWav(out, markAsLast);
|
|
275
|
+
promise.resolve("queued");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 2️⃣ Handle require() assets (resolved as "asset:/...") via Android asset manager
|
|
280
|
+
if (pathOrURL.startsWith("asset:/")) {
|
|
281
|
+
String assetName = pathOrURL.replace("asset:/", "");
|
|
282
|
+
Log.d(TAG, "Detected bundled asset: " + assetName);
|
|
283
|
+
File tmp = new File(reactCtx.getCacheDir(), "asset_" + System.currentTimeMillis() + ".wav");
|
|
284
|
+
try (java.io.InputStream in = reactCtx.getAssets().open(assetName);
|
|
285
|
+
FileOutputStream out = new FileOutputStream(tmp)) {
|
|
286
|
+
byte[] buf = new byte[8192];
|
|
287
|
+
int len;
|
|
288
|
+
while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
|
|
289
|
+
}
|
|
290
|
+
Log.d(TAG, "Copied asset to: " + tmp.getAbsolutePath());
|
|
291
|
+
tts.playWav(tmp, markAsLast);
|
|
292
|
+
promise.resolve("queued");
|
|
207
293
|
return;
|
|
208
294
|
}
|
|
295
|
+
|
|
296
|
+
// --- file:// handling ---
|
|
209
297
|
String path = pathOrURL;
|
|
210
298
|
if (isFileUrl(pathOrURL)) {
|
|
211
299
|
Uri u = Uri.parse(pathOrURL);
|
|
212
300
|
File f = new File(u.getPath());
|
|
213
301
|
path = f.getAbsolutePath();
|
|
302
|
+
Log.d(TAG, "Resolved file:// URL to path: " + path);
|
|
214
303
|
}
|
|
304
|
+
|
|
215
305
|
if (path == null) {
|
|
306
|
+
Log.e(TAG, "Invalid or null path provided");
|
|
216
307
|
promise.reject("bad_path", "Invalid file URL");
|
|
217
308
|
return;
|
|
218
309
|
}
|
|
310
|
+
|
|
219
311
|
File f = new File(path);
|
|
220
312
|
if (!f.exists()) {
|
|
313
|
+
Log.e(TAG, "File does not exist at path: " + path);
|
|
221
314
|
promise.reject("file_missing", "WAV file does not exist at path");
|
|
222
315
|
return;
|
|
223
316
|
}
|
|
317
|
+
|
|
318
|
+
Log.d(TAG, "Playing WAV from path: " + path + " (exists=" + f.exists() + ")");
|
|
224
319
|
tts.playWav(f, markAsLast);
|
|
320
|
+
Log.d(TAG, "Queued playback successfully for: " + path);
|
|
225
321
|
promise.resolve("queued");
|
|
322
|
+
|
|
226
323
|
} catch (Exception e) {
|
|
324
|
+
Log.e(TAG, "PlayWavError: " + e.getMessage(), e);
|
|
227
325
|
promise.reject("PlayWavError", e.getMessage(), e);
|
|
228
326
|
}
|
|
229
327
|
}
|
|
230
|
-
|
|
328
|
+
|
|
231
329
|
@ReactMethod
|
|
232
330
|
public void playBuffer(ReadableMap desc, Promise promise) {
|
|
233
331
|
try {
|
|
@@ -332,27 +332,85 @@ RCT_EXPORT_METHOD(playWav:(NSString *)pathOrURL
|
|
|
332
332
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
333
333
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
334
334
|
{
|
|
335
|
-
if (!self.tts) {
|
|
335
|
+
if (!self.tts) {
|
|
336
|
+
reject(@"no_tts", @"Call initAll first", nil);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (pathOrURL == nil || pathOrURL.length == 0) {
|
|
341
|
+
reject(@"bad_path", @"Empty pathOrURL", nil);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
336
344
|
|
|
337
|
-
NSURL *
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
345
|
+
NSURL *fileURL = nil;
|
|
346
|
+
|
|
347
|
+
// 1️⃣ Handle http(s) URLs — download to temporary file first
|
|
348
|
+
if ([pathOrURL hasPrefix:@"http://"] || [pathOrURL hasPrefix:@"https://"]) {
|
|
349
|
+
NSLog(@"[TTS] Downloading asset from URL: %@", pathOrURL);
|
|
350
|
+
NSURL *remoteURL = [NSURL URLWithString:pathOrURL];
|
|
351
|
+
if (!remoteURL) {
|
|
352
|
+
reject(@"bad_url", @"Invalid remote URL", nil);
|
|
343
353
|
return;
|
|
344
354
|
}
|
|
345
|
-
|
|
346
|
-
|
|
355
|
+
|
|
356
|
+
NSData *data = [NSData dataWithContentsOfURL:remoteURL];
|
|
357
|
+
if (!data) {
|
|
358
|
+
reject(@"download_failed", @"Failed to download remote asset", nil);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
NSString *tempName = [NSString stringWithFormat:@"rn_asset_%f.wav", [[NSDate date] timeIntervalSince1970]];
|
|
363
|
+
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName];
|
|
364
|
+
if (![data writeToFile:tempPath atomically:YES]) {
|
|
365
|
+
reject(@"write_failed", @"Failed to write temporary file", nil);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
fileURL = [NSURL fileURLWithPath:tempPath];
|
|
369
|
+
NSLog(@"[TTS] Downloaded to temp file: %@", tempPath);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 2️⃣ Handle bundled asset:/ paths (copied from main bundle)
|
|
373
|
+
else if ([pathOrURL hasPrefix:@"asset:/"]) {
|
|
374
|
+
NSString *assetName = [pathOrURL stringByReplacingOccurrencesOfString:@"asset:/" withString:@""];
|
|
375
|
+
NSLog(@"[TTS] Detected bundled asset: %@", assetName);
|
|
376
|
+
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:[assetName stringByDeletingPathExtension]
|
|
377
|
+
ofType:[assetName pathExtension]];
|
|
378
|
+
if (!bundlePath) {
|
|
379
|
+
reject(@"asset_missing", [NSString stringWithFormat:@"Asset not found in bundle: %@", assetName], nil);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
// Copy to temp file so we have a writable/accessible URL
|
|
383
|
+
NSString *tempName = [NSString stringWithFormat:@"asset_%f.wav", [[NSDate date] timeIntervalSince1970]];
|
|
384
|
+
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName];
|
|
385
|
+
NSError *copyError = nil;
|
|
386
|
+
[[NSFileManager defaultManager] copyItemAtPath:bundlePath toPath:tempPath error:©Error];
|
|
387
|
+
if (copyError) {
|
|
388
|
+
reject(@"asset_copy_failed", copyError.localizedDescription, copyError);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
fileURL = [NSURL fileURLWithPath:tempPath];
|
|
392
|
+
NSLog(@"[TTS] Copied bundled asset to temp: %@", tempPath);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 3️⃣ Handle file:// URLs
|
|
396
|
+
else if ([pathOrURL hasPrefix:@"file://"]) {
|
|
397
|
+
fileURL = [NSURL URLWithString:pathOrURL];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 4️⃣ Otherwise assume direct local path
|
|
401
|
+
else {
|
|
402
|
+
fileURL = [NSURL fileURLWithPath:pathOrURL];
|
|
347
403
|
}
|
|
348
404
|
|
|
349
|
-
|
|
350
|
-
|
|
405
|
+
// 5️⃣ Verify existence
|
|
406
|
+
if (!fileURL || ![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
|
|
407
|
+
reject(@"file_missing", [NSString stringWithFormat:@"File missing: %@", fileURL.path], nil);
|
|
351
408
|
return;
|
|
352
409
|
}
|
|
353
410
|
|
|
354
|
-
//
|
|
355
|
-
[
|
|
411
|
+
// 6️⃣ Play through TTS engine (queued)
|
|
412
|
+
NSLog(@"[TTS] Playing file via DaVoiceTTS: %@", fileURL.path);
|
|
413
|
+
[self.tts playWav:fileURL markAsLastUtterance:markAsLast.boolValue];
|
|
356
414
|
resolve(@"queued");
|
|
357
415
|
}
|
|
358
416
|
|
package/package.json
CHANGED
package/speech/index.ts
CHANGED
|
@@ -408,18 +408,40 @@ private rewireListenersForMode() {
|
|
|
408
408
|
|
|
409
409
|
// --- NEW: TTS passthroughs for external audio ---
|
|
410
410
|
/** Queue a WAV file (local path, file:// URL, or require() asset). */
|
|
411
|
-
async playWav(pathOrURL:
|
|
411
|
+
async playWav(pathOrURL: any, markAsLast = true) {
|
|
412
412
|
// NEW: resolve require() assets to actual file path/URI
|
|
413
|
+
console.log('[Speech.playWav] called with:', pathOrURL, '| type:', typeof pathOrURL);
|
|
414
|
+
const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource').default;
|
|
415
|
+
|
|
413
416
|
const asset = resolveAssetSource(pathOrURL);
|
|
414
|
-
|
|
417
|
+
console.log('[Speech.playWav] resolveAssetSource ->', asset);
|
|
418
|
+
|
|
419
|
+
let realPath = asset?.uri ?? pathOrURL; // fallback keeps string path intact
|
|
420
|
+
console.log('[Speech.playWav] resolved realPath:', realPath);
|
|
421
|
+
|
|
422
|
+
// ✅ Coerce to string explicitly
|
|
423
|
+
if (typeof realPath !== 'string') {
|
|
424
|
+
realPath = String(realPath);
|
|
425
|
+
console.log('[Speech.playWav] converted ?? realPath:', realPath);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log('[Speech.playWav] before checking ios realPath:', realPath);
|
|
415
429
|
|
|
416
430
|
// Prefer unified iOS bridge if present
|
|
417
431
|
if (Platform.OS === 'ios' && NativeSpeech?.playWav) {
|
|
418
432
|
return NativeSpeech.playWav(realPath, markAsLast);
|
|
419
433
|
}
|
|
434
|
+
console.log('[Speech.playWav] after checking ios realPath:', realPath);
|
|
435
|
+
console.log('[Speech.playWav] after checking ios realPath:', typeof(realPath));
|
|
420
436
|
|
|
421
437
|
// Fallback: direct TTS bridge (Android + iOS fallback)
|
|
422
|
-
if (!NativeTTS?.playWav)
|
|
438
|
+
if (!NativeTTS?.playWav) {
|
|
439
|
+
console.log('[Speech.playWav] NativeTTS:', NativeTTS);
|
|
440
|
+
if (NativeTTS)
|
|
441
|
+
console.log('[Speech.playWav] NativeTTS.playWav :', NativeTTS.playWav);
|
|
442
|
+
throw new Error('playWav not available on this platform.');
|
|
443
|
+
}
|
|
444
|
+
console.log('[Speech.playWav] calling NativeTTS.playWav with type of realPath:', typeof(realPath));
|
|
423
445
|
return NativeTTS.playWav(realPath, markAsLast);
|
|
424
446
|
}
|
|
425
447
|
|