react-native-davoice-tts 1.0.252 → 1.0.253
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 +1 -1
- package/ios/SpeechBridge/SpeechBridge.m +18 -0
- package/package.json +1 -1
- package/speech/index.ts +56 -19
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.126" # 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 .
|
|
@@ -161,6 +161,24 @@ RCT_EXPORT_MODULE(SpeechBridge)
|
|
|
161
161
|
- (NSURL *)resolveLocalURLFromPathOrURL:(NSString *)pathOrURL
|
|
162
162
|
{
|
|
163
163
|
if (!pathOrURL || (id)pathOrURL == [NSNull null] || pathOrURL.length == 0) return nil;
|
|
164
|
+
// ✅ Handle http(s): download to tmp and return file URL
|
|
165
|
+
if ([pathOrURL hasPrefix:@"http://"] || [pathOrURL hasPrefix:@"https://"]) {
|
|
166
|
+
NSURL *remoteURL = [NSURL URLWithString:pathOrURL];
|
|
167
|
+
if (!remoteURL) return nil;
|
|
168
|
+
|
|
169
|
+
NSData *data = [NSData dataWithContentsOfURL:remoteURL];
|
|
170
|
+
if (!data) return nil;
|
|
171
|
+
|
|
172
|
+
// keep extension if possible
|
|
173
|
+
NSString *ext = remoteURL.pathExtension.length ? remoteURL.pathExtension : @"bin";
|
|
174
|
+
NSString *tempName = [NSString stringWithFormat:@"rn_asset_%f.%@", [[NSDate date] timeIntervalSince1970], ext];
|
|
175
|
+
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName];
|
|
176
|
+
|
|
177
|
+
[[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil];
|
|
178
|
+
if (![data writeToFile:tempPath atomically:YES]) return nil;
|
|
179
|
+
|
|
180
|
+
return [NSURL fileURLWithPath:tempPath];
|
|
181
|
+
}
|
|
164
182
|
|
|
165
183
|
// Already a file URL
|
|
166
184
|
if ([pathOrURL hasPrefix:@"file://"]) {
|
package/package.json
CHANGED
package/speech/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { NativeModules, NativeEventEmitter, DeviceEventEmitter, Platform } from 'react-native';
|
|
3
3
|
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
// -------------------- VERBOSE LOGGING --------------------
|
|
7
6
|
const VERBOSE = true;
|
|
8
7
|
const PFX = '[SpeechJS]';
|
|
@@ -109,6 +108,47 @@ export type ExternalPCM = {
|
|
|
109
108
|
};
|
|
110
109
|
|
|
111
110
|
class Speech {
|
|
111
|
+
// ---- MIN: serialize TTS + wait-for-finished ----
|
|
112
|
+
private ttsChain: Promise<void> = Promise.resolve();
|
|
113
|
+
private ttsPendingResolve: (() => void) | null = null;
|
|
114
|
+
private ttsPendingTimeout: any = null;
|
|
115
|
+
|
|
116
|
+
private _onNativeFinishedSpeaking() {
|
|
117
|
+
dbg('[EVENT onFinishedSpeaking]');
|
|
118
|
+
// 1) let app callback run
|
|
119
|
+
try { this.handlers.onFinishedSpeaking(); } catch (e) { dbgErr('onFinishedSpeaking handler error', String(e)); }
|
|
120
|
+
|
|
121
|
+
// 2) resolve the internal await (if any)
|
|
122
|
+
if (this.ttsPendingTimeout) { clearTimeout(this.ttsPendingTimeout); this.ttsPendingTimeout = null; }
|
|
123
|
+
const r = this.ttsPendingResolve;
|
|
124
|
+
this.ttsPendingResolve = null;
|
|
125
|
+
if (r) r();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private _nativeSpeak(text: string, speakerId: number, s: number) {
|
|
129
|
+
if (Platform.OS === 'ios' && NativeSpeech?.speak) return (NativeSpeech as any).speak(text, speakerId, s);
|
|
130
|
+
if (!NativeTTS?.speak) throw new Error('TTS speak not available');
|
|
131
|
+
return (NativeTTS as any).speak(text, speakerId, s);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _speakAndWait(text: string, speakerId: number, s: number, timeoutMs = 60000) {
|
|
135
|
+
return new Promise<void>((resolve, reject) => {
|
|
136
|
+
this.ttsPendingResolve = resolve;
|
|
137
|
+
// safety: never hang forever
|
|
138
|
+
this.ttsPendingTimeout = setTimeout(() => {
|
|
139
|
+
this.ttsPendingResolve = null;
|
|
140
|
+
reject(new Error('TTS timeout waiting for onFinishedSpeaking'));
|
|
141
|
+
}, timeoutMs);
|
|
142
|
+
try {
|
|
143
|
+
this._nativeSpeak(text, speakerId, s);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (this.ttsPendingTimeout) { clearTimeout(this.ttsPendingTimeout); this.ttsPendingTimeout = null; }
|
|
146
|
+
this.ttsPendingResolve = null;
|
|
147
|
+
reject(e as any);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
112
152
|
private sttEmitter: NativeEventEmitter | null = null;
|
|
113
153
|
private ttsEmitter: NativeEventEmitter | typeof DeviceEventEmitter | null = null;
|
|
114
154
|
private unifiedEmitter: NativeEventEmitter | null = null;
|
|
@@ -374,8 +414,9 @@ class Speech {
|
|
|
374
414
|
if (Platform.OS === 'ios' && (NativeSpeech as any)?.pauseMicrophoneAsync) {
|
|
375
415
|
dbg('IOS [pauseMicrophone] using NativeSpeech.pauseMicrophoneAsync()');
|
|
376
416
|
try {
|
|
377
|
-
await (NativeSpeech as any).pauseMicrophoneAsync();
|
|
378
|
-
dbg('
|
|
417
|
+
const r = await (NativeSpeech as any).pauseMicrophoneAsync(1000);
|
|
418
|
+
dbg('pauseMicrophoneAsync result', r);
|
|
419
|
+
if (r?.ok === false) dbgErr('pauseMicrophoneAsync failed', r?.reason);
|
|
379
420
|
return;
|
|
380
421
|
} catch (e) {
|
|
381
422
|
dbgErr('IOS [pauseMicrophone] NativeSpeech.pauseMicrophoneAsync() ERROR:', String(e));
|
|
@@ -403,7 +444,8 @@ class Speech {
|
|
|
403
444
|
if (Platform.OS === 'ios' && (NativeSpeech as any)?.unPauseMicrophoneAsync) {
|
|
404
445
|
dbg('IOS [unPauseMicrophone] using NativeSpeech.unPauseMicrophoneAsync()');
|
|
405
446
|
try {
|
|
406
|
-
await (NativeSpeech as any).unPauseMicrophoneAsync();
|
|
447
|
+
const r = await (NativeSpeech as any).unPauseMicrophoneAsync(1000);
|
|
448
|
+
if (r?.ok === false) dbgErr('pauseMicrophoneAsync failed', r?.reason);
|
|
407
449
|
dbg('IOS [unPauseMicrophone] NativeSpeech.unPauseMicrophoneAsync() DONE');
|
|
408
450
|
return;
|
|
409
451
|
} catch (e) {
|
|
@@ -495,17 +537,10 @@ class Speech {
|
|
|
495
537
|
// sanitize and invert (avoid NaN/undefined/null)
|
|
496
538
|
// Reverse speed to length.
|
|
497
539
|
const s = Number.isFinite(speed as number) && speed !== 0 ? 1.0 / (speed as number) : 1.0;
|
|
498
|
-
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return (NativeSpeech as any).speak(text, speakerId, s);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (!NativeTTS?.speak) throw new Error('TTS speak not available');
|
|
506
|
-
|
|
507
|
-
// ✅ always call native with 3 args (text, speakerId, speed)
|
|
508
|
-
return (NativeTTS as any).speak(text, speakerId, s);
|
|
540
|
+
this.ensureListeners();
|
|
541
|
+
// MIN: serialize + await actual completion (event-driven)
|
|
542
|
+
this.ttsChain = this.ttsChain.then(() => this._speakAndWait(text, speakerId, s));
|
|
543
|
+
return this.ttsChain;
|
|
509
544
|
}
|
|
510
545
|
|
|
511
546
|
async stopSpeaking() {
|
|
@@ -645,7 +680,7 @@ class Speech {
|
|
|
645
680
|
onSpeechResults: (e) => this.handlers.onSpeechResults(e),
|
|
646
681
|
onSpeechPartialResults: (e) => this.handlers.onSpeechPartialResults(e),
|
|
647
682
|
onSpeechVolumeChanged: (e) => this.handlers.onSpeechVolumeChanged(e),
|
|
648
|
-
onFinishedSpeaking: () => this.
|
|
683
|
+
onFinishedSpeaking: () => this._onNativeFinishedSpeaking(),
|
|
649
684
|
};
|
|
650
685
|
(Object.keys(map) as NativeEventName[]).forEach((name) => {
|
|
651
686
|
try {
|
|
@@ -677,9 +712,11 @@ class Speech {
|
|
|
677
712
|
}
|
|
678
713
|
if (this.ttsEmitter) {
|
|
679
714
|
try {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
);
|
|
715
|
+
// MIN: prevent duplicate listeners across Fast Refresh / reload
|
|
716
|
+
const g: any = globalThis as any;
|
|
717
|
+
try { g.__SpeechJS_finishedSub?.remove?.(); } catch {}
|
|
718
|
+
const sub = this.ttsEmitter.addListener('onFinishedSpeaking', () => this._onNativeFinishedSpeaking());
|
|
719
|
+
g.__SpeechJS_finishedSub = sub;
|
|
683
720
|
this.subs.push(sub);
|
|
684
721
|
} catch {}
|
|
685
722
|
}
|