react-native-davoice-tts 1.0.362 → 1.0.364
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 +19 -0
- package/TTSRNBridge.podspec +1 -1
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
- package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar +0 -0
- package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.md5 +1 -1
- package/android/libs/com/davoice/tts/1.0.0/tts-1.0.0.aar.sha1 +1 -1
- package/android/src/main/java/com/davoice/stt/rn/STTModule.kt +3 -1
- package/ios/SpeechBridge/SpeechBridge.m +3 -2
- package/ios/TTSRNBridge/DaVoiceTTSBridge.m +144 -3
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/DavoiceTTS +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +403 -368
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/DavoiceTTS +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +403 -368
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json +403 -368
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/_CodeSignature/CodeResources +12 -12
- package/package.json +1 -1
- package/speech/index.ts +22 -7
package/README.md
CHANGED
|
@@ -138,6 +138,7 @@ The unified API is intended for real voice flows where STT and TTS are part of t
|
|
|
138
138
|
Common methods:
|
|
139
139
|
|
|
140
140
|
- `initAll({ locale, model, timeoutMs?, onboardingJsonPath? })`
|
|
141
|
+
- `initTTS({ model })` — initialize only TTS; does not create or start STT
|
|
141
142
|
- `destroyAll()`
|
|
142
143
|
- `start(locale, options?)`
|
|
143
144
|
- `stop()`
|
|
@@ -169,6 +170,24 @@ Unified events:
|
|
|
169
170
|
- `onNewSpeechWAV`
|
|
170
171
|
Android-only remote STT flow event.
|
|
171
172
|
|
|
173
|
+
### TTS-Only Through the Speech API
|
|
174
|
+
|
|
175
|
+
Use `initTTS()` when the application needs synthesis/playback without speech
|
|
176
|
+
recognition:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import Speech from 'react-native-davoice-tts/speech';
|
|
180
|
+
|
|
181
|
+
await Speech.setLicense('YOUR_LICENSE_KEY');
|
|
182
|
+
await Speech.initTTS({ model });
|
|
183
|
+
await Speech.speak('Hello world');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
On both iOS and Android this path initializes only the standalone TTS engine.
|
|
187
|
+
It does not construct or start the STT engine, request microphone permissions,
|
|
188
|
+
or begin microphone capture. `speak()`, `stopSpeaking()`, `playWav()`, and
|
|
189
|
+
`playBuffer()` remain on the standalone TTS bridge until `initAll()` is used.
|
|
190
|
+
|
|
172
191
|
### iOS Permission Helpers
|
|
173
192
|
|
|
174
193
|
The unified API also exposes iOS permission helpers:
|
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.292" # 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 .
|
|
Binary file
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#
|
|
2
|
-
gradle.version=9
|
|
1
|
+
#Mon Aug 17 12:10:54 IDT 2026
|
|
2
|
+
gradle.version=8.9
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20bac695ad8c44a85e5ca9ece89a26bc tts-1.0.0.aar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
0d733323fe3b29e2ac003a9e677fac17fee18c31 tts-1.0.0.aar
|
|
@@ -59,7 +59,9 @@ class STTModule(private val rc: ReactApplicationContext)
|
|
|
59
59
|
promise.reject("invalid_args", "Missing licenseKey")
|
|
60
60
|
return
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
// Licensing is process-global. Do not construct STT merely to store the
|
|
63
|
+
// key: Speech.initTTS() must remain a genuinely TTS-only path.
|
|
64
|
+
promise.resolve(LicenseManager.setLicenseKey(licenseKey))
|
|
63
65
|
} catch (t: Throwable) {
|
|
64
66
|
promise.reject("LicenseError", t.message, t)
|
|
65
67
|
}
|
|
@@ -403,8 +403,9 @@ RCT_EXPORT_METHOD(setLicense:(NSString *)licenseKey
|
|
|
403
403
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
404
404
|
self.lastLicenseKey = licenseKey;
|
|
405
405
|
BOOL ttsOk = [DaVoiceTTS activateLicenseWithLicenseKey:licenseKey];
|
|
406
|
-
|
|
407
|
-
|
|
406
|
+
// Keep licensing side-effect free. initAll()/the explicit STT APIs create
|
|
407
|
+
// STT through ensureSTT(), which reapplies lastLicenseKey at that point.
|
|
408
|
+
BOOL sttOk = self.stt ? [self.stt setLicenseWithLicenseKey:licenseKey] : YES;
|
|
408
409
|
resolve(@(ttsOk && sttOk));
|
|
409
410
|
});
|
|
410
411
|
}
|
|
@@ -2,6 +2,62 @@
|
|
|
2
2
|
#import "DaVoiceTTSBridge.h"
|
|
3
3
|
#import <React/RCTLog.h>
|
|
4
4
|
#import <React/RCTConvert.h>
|
|
5
|
+
#import <AVFAudio/AVFAudio.h>
|
|
6
|
+
|
|
7
|
+
static NSData *DTTSBase64Decode(NSString *base64) {
|
|
8
|
+
if (!base64 || (id)base64 == [NSNull null]) return nil;
|
|
9
|
+
return [[NSData alloc] initWithBase64EncodedString:base64 options:0];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static AVAudioPCMBuffer *DTTSMakeMonoF32Buffer(NSData *raw,
|
|
13
|
+
double sampleRate,
|
|
14
|
+
NSString *format,
|
|
15
|
+
NSInteger channels,
|
|
16
|
+
BOOL interleaved)
|
|
17
|
+
{
|
|
18
|
+
if (!raw || raw.length == 0 || sampleRate <= 0 || channels < 1) return nil;
|
|
19
|
+
|
|
20
|
+
AVAudioFormat *outputFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:sampleRate
|
|
21
|
+
channels:1];
|
|
22
|
+
NSString *normalizedFormat = format.lowercaseString;
|
|
23
|
+
NSUInteger bytesPerSample = [normalizedFormat isEqualToString:@"i16"] ? 2 :
|
|
24
|
+
([normalizedFormat isEqualToString:@"f32"] ? 4 : 0);
|
|
25
|
+
if (bytesPerSample == 0 || raw.length % (bytesPerSample * channels) != 0) return nil;
|
|
26
|
+
|
|
27
|
+
NSUInteger frameCount = raw.length / (bytesPerSample * channels);
|
|
28
|
+
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:outputFormat
|
|
29
|
+
frameCapacity:(AVAudioFrameCount)frameCount];
|
|
30
|
+
buffer.frameLength = (AVAudioFrameCount)frameCount;
|
|
31
|
+
float *output = buffer.floatChannelData[0];
|
|
32
|
+
|
|
33
|
+
if ([normalizedFormat isEqualToString:@"i16"]) {
|
|
34
|
+
const int16_t *input = (const int16_t *)raw.bytes;
|
|
35
|
+
for (NSUInteger frame = 0; frame < frameCount; ++frame) {
|
|
36
|
+
int64_t sum = 0;
|
|
37
|
+
for (NSInteger channel = 0; channel < channels; ++channel) {
|
|
38
|
+
NSUInteger index = interleaved || channels == 1
|
|
39
|
+
? frame * channels + channel
|
|
40
|
+
: channel * frameCount + frame;
|
|
41
|
+
sum += input[index];
|
|
42
|
+
}
|
|
43
|
+
output[frame] = (float)(sum / (double)channels) / 32768.0f;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
const float *input = (const float *)raw.bytes;
|
|
47
|
+
for (NSUInteger frame = 0; frame < frameCount; ++frame) {
|
|
48
|
+
double sum = 0;
|
|
49
|
+
for (NSInteger channel = 0; channel < channels; ++channel) {
|
|
50
|
+
NSUInteger index = interleaved || channels == 1
|
|
51
|
+
? frame * channels + channel
|
|
52
|
+
: channel * frameCount + frame;
|
|
53
|
+
sum += input[index];
|
|
54
|
+
}
|
|
55
|
+
output[frame] = (float)(sum / channels);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return buffer;
|
|
60
|
+
}
|
|
5
61
|
|
|
6
62
|
@interface DaVoiceTTSBridge()
|
|
7
63
|
@property (nonatomic, strong, nullable) DaVoiceTTS *ttsInstance;
|
|
@@ -56,6 +112,33 @@ RCT_EXPORT_MODULE();
|
|
|
56
112
|
return self;
|
|
57
113
|
}
|
|
58
114
|
|
|
115
|
+
- (NSURL *)resolveLocalURLFromPathOrURL:(NSString *)pathOrURL
|
|
116
|
+
{
|
|
117
|
+
if (!pathOrURL || (id)pathOrURL == [NSNull null] || pathOrURL.length == 0) return nil;
|
|
118
|
+
|
|
119
|
+
if ([pathOrURL hasPrefix:@"http://"] || [pathOrURL hasPrefix:@"https://"]) {
|
|
120
|
+
NSURL *remoteURL = [NSURL URLWithString:pathOrURL];
|
|
121
|
+
NSData *data = remoteURL ? [NSData dataWithContentsOfURL:remoteURL] : nil;
|
|
122
|
+
if (!data) return nil;
|
|
123
|
+
NSString *extension = remoteURL.pathExtension.length ? remoteURL.pathExtension : @"bin";
|
|
124
|
+
NSString *name = [NSString stringWithFormat:@"rn_asset_%f.%@",
|
|
125
|
+
[[NSDate date] timeIntervalSince1970], extension];
|
|
126
|
+
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:name];
|
|
127
|
+
return [data writeToFile:path atomically:YES] ? [NSURL fileURLWithPath:path] : nil;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if ([pathOrURL hasPrefix:@"file://"]) return [NSURL URLWithString:pathOrURL];
|
|
131
|
+
|
|
132
|
+
if ([pathOrURL hasPrefix:@"asset:/"]) {
|
|
133
|
+
NSString *assetName = [pathOrURL substringFromIndex:@"asset:/".length];
|
|
134
|
+
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:assetName.stringByDeletingPathExtension
|
|
135
|
+
ofType:assetName.pathExtension];
|
|
136
|
+
return bundlePath ? [NSURL fileURLWithPath:bundlePath] : nil;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return [NSURL fileURLWithPath:pathOrURL];
|
|
140
|
+
}
|
|
141
|
+
|
|
59
142
|
RCT_EXPORT_METHOD(setLicense:(NSString *)licenseKey
|
|
60
143
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
61
144
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -99,9 +182,11 @@ RCT_EXPORT_METHOD(initTTS:(NSDictionary *)configDict
|
|
|
99
182
|
return;
|
|
100
183
|
}
|
|
101
184
|
|
|
102
|
-
NSURL *modelURL = [modelPath
|
|
103
|
-
|
|
104
|
-
|
|
185
|
+
NSURL *modelURL = [self resolveLocalURLFromPathOrURL:modelPath];
|
|
186
|
+
if (!modelURL) {
|
|
187
|
+
reject(@"bad_model", [NSString stringWithFormat:@"Could not resolve model path: %@", modelPath], nil);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
105
190
|
|
|
106
191
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
107
192
|
NSError *error = nil;
|
|
@@ -179,6 +264,62 @@ RCT_EXPORT_METHOD(initTTS:(NSDictionary *)configDict
|
|
|
179
264
|
});
|
|
180
265
|
}
|
|
181
266
|
|
|
267
|
+
RCT_EXPORT_METHOD(playWav:(NSString *)pathOrURL
|
|
268
|
+
markAsLast:(nonnull NSNumber *)markAsLast
|
|
269
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
270
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
271
|
+
{
|
|
272
|
+
if (!self.ttsInstance) {
|
|
273
|
+
reject(@"no_instance", @"TTS not initialized", nil);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
NSURL *fileURL = [self resolveLocalURLFromPathOrURL:pathOrURL];
|
|
278
|
+
if (!fileURL || ![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
|
|
279
|
+
reject(@"file_missing", [NSString stringWithFormat:@"File missing: %@", fileURL.path ?: pathOrURL], nil);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[self.ttsInstance playWav:fileURL markAsLastUtterance:markAsLast.boolValue];
|
|
284
|
+
resolve(@"queued");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
RCT_EXPORT_METHOD(playBuffer:(NSDictionary *)description
|
|
288
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
289
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
290
|
+
{
|
|
291
|
+
if (!self.ttsInstance) {
|
|
292
|
+
reject(@"no_instance", @"TTS not initialized", nil);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
NSString *base64 = [RCTConvert NSString:description[@"base64"]];
|
|
297
|
+
NSNumber *sampleRate = [RCTConvert NSNumber:description[@"sampleRate"]];
|
|
298
|
+
NSString *format = [RCTConvert NSString:description[@"format"]];
|
|
299
|
+
NSNumber *channels = description[@"channels"] ? [RCTConvert NSNumber:description[@"channels"]] : @(1);
|
|
300
|
+
NSNumber *interleaved = description[@"interleaved"] ? [RCTConvert NSNumber:description[@"interleaved"]] : @(YES);
|
|
301
|
+
NSNumber *markAsLast = description[@"markAsLast"] ? [RCTConvert NSNumber:description[@"markAsLast"]] : @(YES);
|
|
302
|
+
|
|
303
|
+
if (!base64 || !sampleRate || !format) {
|
|
304
|
+
reject(@"invalid_args", @"Missing one of base64/sampleRate/format", nil);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
NSData *raw = DTTSBase64Decode(base64);
|
|
309
|
+
AVAudioPCMBuffer *buffer = DTTSMakeMonoF32Buffer(raw,
|
|
310
|
+
sampleRate.doubleValue,
|
|
311
|
+
format,
|
|
312
|
+
channels.integerValue,
|
|
313
|
+
interleaved.boolValue);
|
|
314
|
+
if (!buffer) {
|
|
315
|
+
reject(@"bad_buffer", @"Unsupported PCM layout, invalid base64, or empty data", nil);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
[self.ttsInstance playBuffer:buffer markAsLastUtterance:markAsLast.boolValue];
|
|
320
|
+
resolve(@"queued");
|
|
321
|
+
}
|
|
322
|
+
|
|
182
323
|
RCT_EXPORT_METHOD(destroy:(RCTPromiseResolveBlock)resolve
|
|
183
324
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
184
325
|
{
|