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.
@@ -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.125" # Update to your package version
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-davoice-tts",
3
- "version": "1.0.252",
3
+ "version": "1.0.253",
4
4
  "description": "tts library for React Native",
5
5
  "main": "tts/index.js",
6
6
  "types": "tts/index.d.ts",
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('IOS [pauseMicrophone] NativeSpeech.pauseMicrophoneAsync() DONE');
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
- // iOS unified
500
- if (Platform.OS === 'ios' && NativeSpeech?.speak) {
501
- // ✅ always call native with 3 args (text, speakerId, speed)
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.handlers.onFinishedSpeaking(),
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
- const sub = this.ttsEmitter.addListener('onFinishedSpeaking', () =>
681
- this.handlers.onFinishedSpeaking(),
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
  }