react-native-davoice-tts 1.0.409 → 1.0.411
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 +20 -0
- package/TTSRNBridge.podspec +1 -1
- 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/tts/rn/DaVoiceTTSBridge.java +67 -0
- package/ios/SpeechBridge/SpeechBridge.m +40 -0
- package/ios/TTSRNBridge/DaVoiceTTSBridge.m +40 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/DavoiceTTS +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Headers/DavoiceTTS-Swift.h +30 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.abi.json +435 -60
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.private.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios.swiftinterface +4 -0
- 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/Headers/DavoiceTTS-Swift.h +60 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.abi.json +435 -60
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/arm64-apple-ios-simulator.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.abi.json +435 -60
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +4 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/TTSRNBridge/DavoiceTTS.xcframework/ios-arm64_x86_64-simulator/DavoiceTTS.framework/Modules/DavoiceTTS.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +4 -0
- 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 +33 -33
- package/package.json +1 -1
- package/speech/index.d.ts +39 -0
- package/speech/index.ts +73 -0
package/README.md
CHANGED
|
@@ -383,5 +383,25 @@ await speech.getVoiceAbilities("Rich");
|
|
|
383
383
|
await speech.changeVoice("Rich");
|
|
384
384
|
await speech.speak("Ok, [drill sergeant] Private, give me 50 push-ups! [regular] And you, Daniel, come with me.");
|
|
385
385
|
```
|
|
386
|
+
#### Voice cloning (your own voices)
|
|
387
|
+
|
|
388
|
+
Turn a recording into a voice: `createVoiceClone()` takes a wav, mp3, m4a,
|
|
389
|
+
aac, ogg or flac file, keeps the best ~6 s of speech (silence trimmed, long
|
|
390
|
+
pauses shortened, cut between words) and saves a scrambled `.emd` voice file.
|
|
391
|
+
It needs the voice encoder for the model's language, `embedder_<code>.dm`
|
|
392
|
+
(`embedder_en.dm` for English). Point `setExternalVoicesPath()` at the folder
|
|
393
|
+
and the voice is listed and selected like a bundled one (the file name is the
|
|
394
|
+
voice name; a bundled voice wins over a file with the same name).
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
const folder = `${RNFS.DocumentDirectoryPath}/voices`;
|
|
398
|
+
await speech.setExternalVoicesPath(folder);
|
|
399
|
+
const r = await speech.createVoiceClone(recordingPath, `${folder}/Me.emd`,
|
|
400
|
+
require('./assets/models/embedder_en.dm'));
|
|
401
|
+
// r.warnings is non-empty when less than 4 s of speech remained.
|
|
402
|
+
await speech.changeVoice('Me');
|
|
403
|
+
await speech.speak('Hello in my own voice.');
|
|
404
|
+
```
|
|
405
|
+
|
|
386
406
|
The existing speak speed argument still applies, with pitch preserved.
|
|
387
407
|
Rebuild the native app after updating the bundled AAR/XCFramework.
|
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.409" # 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 +1 @@
|
|
|
1
|
-
|
|
1
|
+
a619b1a82c872eb95700500b006c8456 tts-1.0.0.aar
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b8d12c2f14c766d8f75e4cd81759a31e14848f54 tts-1.0.0.aar
|
|
@@ -387,6 +387,73 @@ public class DaVoiceTTSBridge extends ReactContextBaseJavaModule {
|
|
|
387
387
|
promise.resolve(abilities);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
private static String stripFileScheme(String path) {
|
|
391
|
+
return path != null && path.startsWith("file://") ? Uri.parse(path).getPath() : path;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Any path form the app may pass (file path, file://, asset:/, http(s), RN release
|
|
395
|
+
// res/raw names, asset pack) -> readable local file, or null.
|
|
396
|
+
@Nullable
|
|
397
|
+
private File resolveAnyFile(String pathOrURL, @Nullable String defaultExt) {
|
|
398
|
+
if (pathOrURL == null || pathOrURL.trim().isEmpty()) return null;
|
|
399
|
+
File direct = new File(stripFileScheme(pathOrURL.trim()));
|
|
400
|
+
if (direct.isFile()) return direct;
|
|
401
|
+
try {
|
|
402
|
+
File f = resolveToLocalFileForRead(pathOrURL, defaultExt);
|
|
403
|
+
if (f != null && f.isFile()) return f;
|
|
404
|
+
} catch (Exception ignored) { }
|
|
405
|
+
File plain = resolvePlainModelToFile(pathOrURL.trim(), defaultExt);
|
|
406
|
+
return plain != null && plain.isFile() ? plain : null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Also look for voices (<name>.emd) in the folder `path`; null/"" removes them.
|
|
410
|
+
@ReactMethod
|
|
411
|
+
public void setExternalVoicesPath(@Nullable String path, Promise promise) {
|
|
412
|
+
WritableArray names = Arguments.createArray();
|
|
413
|
+
try {
|
|
414
|
+
if (tts != null) {
|
|
415
|
+
for (String name : tts.setExternalVoicesPath(stripFileScheme(path))) names.pushString(name);
|
|
416
|
+
}
|
|
417
|
+
} catch (Exception e) {
|
|
418
|
+
Log.w(TAG, "setExternalVoicesPath failed: " + e.getMessage());
|
|
419
|
+
}
|
|
420
|
+
promise.resolve(names);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Reference audio (wav/mp3/m4a/...) -> scrambled .emd at outputPath, using the
|
|
424
|
+
// voice encoder embedder_<code>.dm. Runs off the bridge thread (takes seconds).
|
|
425
|
+
@ReactMethod
|
|
426
|
+
public void createVoiceClone(String inputAudio, String outputPath, String embedder, Promise promise) {
|
|
427
|
+
initExecutor.execute(() -> {
|
|
428
|
+
try {
|
|
429
|
+
if (inputAudio == null || outputPath == null || embedder == null) {
|
|
430
|
+
promise.reject("invalid_args", "createVoiceClone(inputAudio, outputPath, embedder)");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
String ext = inputAudio.contains(".") ? inputAudio.substring(inputAudio.lastIndexOf('.') + 1) : "wav";
|
|
434
|
+
File input = resolveAnyFile(inputAudio, ext.length() <= 5 ? ext : "wav");
|
|
435
|
+
if (input == null) { promise.reject("input_missing", "Input audio missing/unresolvable: " + inputAudio); return; }
|
|
436
|
+
File embedderFile = resolveAnyFile(embedder, "dm");
|
|
437
|
+
String embedderPath = embedderFile != null ? embedderFile.getAbsolutePath() : embedder; // else an APK asset name
|
|
438
|
+
java.util.Map<String, Object> result =
|
|
439
|
+
tts.createVoiceClone(input.getAbsolutePath(), stripFileScheme(outputPath), embedderPath);
|
|
440
|
+
WritableMap map = Arguments.createMap();
|
|
441
|
+
map.putString("path", String.valueOf(result.get("path")));
|
|
442
|
+
map.putString("voice", String.valueOf(result.get("voice")));
|
|
443
|
+
map.putDouble("seconds", ((Number) result.get("seconds")).doubleValue());
|
|
444
|
+
for (String key : new String[]{"warnings", "report"}) {
|
|
445
|
+
WritableArray list = Arguments.createArray();
|
|
446
|
+
Object value = result.get(key);
|
|
447
|
+
if (value instanceof java.util.List) for (Object line : (java.util.List<?>) value) list.pushString(String.valueOf(line));
|
|
448
|
+
map.putArray(key, list);
|
|
449
|
+
}
|
|
450
|
+
promise.resolve(map);
|
|
451
|
+
} catch (Exception e) {
|
|
452
|
+
promise.reject("voice_clone_failed", e.getMessage(), e);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
390
457
|
@ReactMethod
|
|
391
458
|
public void changeVoice(String name, Promise promise) {
|
|
392
459
|
try {
|
|
@@ -1216,6 +1216,46 @@ RCT_EXPORT_METHOD(getVoiceAbilities:(NSString *)name
|
|
|
1216
1216
|
resolve(self.tts && name ? ([self.tts getVoiceAbilities:name] ?: @[]) : @[]);
|
|
1217
1217
|
}
|
|
1218
1218
|
|
|
1219
|
+
RCT_EXPORT_METHOD(setExternalVoicesPath:(NSString *)path
|
|
1220
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1221
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1222
|
+
{
|
|
1223
|
+
DaVoiceTTS *tts = self.tts;
|
|
1224
|
+
if (!tts) { resolve(@[]); return; }
|
|
1225
|
+
NSString *folder = (path && (id)path != [NSNull null] && path.length) ? path : nil;
|
|
1226
|
+
if ([folder hasPrefix:@"file://"]) folder = [NSURL URLWithString:folder].path;
|
|
1227
|
+
[tts setExternalVoicesPath:folder completion:^(NSArray<NSString *> *names) { resolve(names ?: @[]); }];
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Reference audio (wav/mp3/m4a/...) -> scrambled .emd at outputPath, using the
|
|
1231
|
+
// voice encoder embedder_<code>.dm. Runs off the bridge queue (takes seconds).
|
|
1232
|
+
RCT_EXPORT_METHOD(createVoiceClone:(NSString *)inputAudio
|
|
1233
|
+
outputPath:(NSString *)outputPath
|
|
1234
|
+
embedder:(NSString *)embedder
|
|
1235
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
1236
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
1237
|
+
{
|
|
1238
|
+
if (!inputAudio || !outputPath || !embedder || (id)outputPath == [NSNull null]) {
|
|
1239
|
+
reject(@"invalid_args", @"createVoiceClone(inputAudio, outputPath, embedder)", nil);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
1243
|
+
BOOL remoteInput = [inputAudio hasPrefix:@"http://"] || [inputAudio hasPrefix:@"https://"];
|
|
1244
|
+
BOOL remoteEmbedder = [embedder hasPrefix:@"http://"] || [embedder hasPrefix:@"https://"];
|
|
1245
|
+
NSURL *inputURL = [self resolveLocalURLFromPathOrURL:inputAudio];
|
|
1246
|
+
NSURL *embedderURL = [self resolveLocalURLFromPathOrURL:embedder];
|
|
1247
|
+
NSString *output = [outputPath hasPrefix:@"file://"] ? [NSURL URLWithString:outputPath].path : outputPath;
|
|
1248
|
+
NSError *error = nil;
|
|
1249
|
+
NSDictionary *result = (inputURL && embedderURL)
|
|
1250
|
+
? [DaVoiceTTS createVoiceClone:inputURL.path outputPath:output embedderPath:embedderURL.path error:&error]
|
|
1251
|
+
: nil;
|
|
1252
|
+
if (remoteInput && inputURL) [[NSFileManager defaultManager] removeItemAtURL:inputURL error:nil];
|
|
1253
|
+
if (remoteEmbedder && embedderURL) [[NSFileManager defaultManager] removeItemAtURL:embedderURL error:nil];
|
|
1254
|
+
if (result) resolve(result);
|
|
1255
|
+
else reject(@"voice_clone_failed", error.localizedDescription ?: @"Could not resolve the input audio or embedder path", error);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1219
1259
|
RCT_EXPORT_METHOD(changeVoice:(NSString *)name
|
|
1220
1260
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
1221
1261
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
@@ -289,6 +289,46 @@ RCT_EXPORT_METHOD(getVoiceAbilities:(NSString *)name
|
|
|
289
289
|
resolve(self.ttsInstance && name ? ([self.ttsInstance getVoiceAbilities:name] ?: @[]) : @[]);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
RCT_EXPORT_METHOD(setExternalVoicesPath:(NSString *)path
|
|
293
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
294
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
295
|
+
{
|
|
296
|
+
DaVoiceTTS *tts = self.ttsInstance;
|
|
297
|
+
if (!tts) { resolve(@[]); return; }
|
|
298
|
+
NSString *folder = (path && (id)path != [NSNull null] && path.length) ? path : nil;
|
|
299
|
+
if ([folder hasPrefix:@"file://"]) folder = [NSURL URLWithString:folder].path;
|
|
300
|
+
[tts setExternalVoicesPath:folder completion:^(NSArray<NSString *> *names) { resolve(names ?: @[]); }];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Reference audio (wav/mp3/m4a/...) -> scrambled .emd at outputPath, using the
|
|
304
|
+
// voice encoder embedder_<code>.dm. Runs off the bridge queue (takes seconds).
|
|
305
|
+
RCT_EXPORT_METHOD(createVoiceClone:(NSString *)inputAudio
|
|
306
|
+
outputPath:(NSString *)outputPath
|
|
307
|
+
embedder:(NSString *)embedder
|
|
308
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
309
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
310
|
+
{
|
|
311
|
+
if (!inputAudio || !outputPath || !embedder || (id)outputPath == [NSNull null]) {
|
|
312
|
+
reject(@"invalid_args", @"createVoiceClone(inputAudio, outputPath, embedder)", nil);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
316
|
+
BOOL remoteInput = [inputAudio hasPrefix:@"http://"] || [inputAudio hasPrefix:@"https://"];
|
|
317
|
+
BOOL remoteEmbedder = [embedder hasPrefix:@"http://"] || [embedder hasPrefix:@"https://"];
|
|
318
|
+
NSURL *inputURL = [self resolveLocalURLFromPathOrURL:inputAudio];
|
|
319
|
+
NSURL *embedderURL = [self resolveLocalURLFromPathOrURL:embedder];
|
|
320
|
+
NSString *output = [outputPath hasPrefix:@"file://"] ? [NSURL URLWithString:outputPath].path : outputPath;
|
|
321
|
+
NSError *error = nil;
|
|
322
|
+
NSDictionary *result = (inputURL && embedderURL)
|
|
323
|
+
? [DaVoiceTTS createVoiceClone:inputURL.path outputPath:output embedderPath:embedderURL.path error:&error]
|
|
324
|
+
: nil;
|
|
325
|
+
if (remoteInput && inputURL) [[NSFileManager defaultManager] removeItemAtURL:inputURL error:nil];
|
|
326
|
+
if (remoteEmbedder && embedderURL) [[NSFileManager defaultManager] removeItemAtURL:embedderURL error:nil];
|
|
327
|
+
if (result) resolve(result);
|
|
328
|
+
else reject(@"voice_clone_failed", error.localizedDescription ?: @"Could not resolve the input audio or embedder path", error);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
292
332
|
RCT_EXPORT_METHOD(changeVoice:(NSString *)name
|
|
293
333
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
294
334
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
Binary file
|
|
@@ -361,6 +361,36 @@ SWIFT_CLASS("_TtC10DavoiceTTS10DaVoiceTTS")
|
|
|
361
361
|
/// 50! [regular] Thanks.” Unmatched tags use the default voice; tags are never
|
|
362
362
|
/// spoken. Empty for voices without variants, v1 models, or after destroy().
|
|
363
363
|
- (NSArray<NSString *> * _Nonnull)getVoiceAbilities:(NSString * _Nonnull)name SWIFT_WARN_UNUSED_RESULT;
|
|
364
|
+
/// Also look for voices in the folder <code>path</code>: <name>.emd / <name>.npy files,
|
|
365
|
+
/// e.g. made by createVoiceClone(). They are listed by getAllVoices()
|
|
366
|
+
/// after the bundled voices and selected with changeVoice() like them; a
|
|
367
|
+
/// bundled voice wins over an external file with the same name. Call it
|
|
368
|
+
/// again to pick up new files; nil or “” removes the external voices.
|
|
369
|
+
/// <code>completion</code> gets the external voice names found ([] for a v1 model or a
|
|
370
|
+
/// missing folder).
|
|
371
|
+
- (void)setExternalVoicesPath:(NSString * _Nullable)path completion:(void (^ _Nullable)(NSArray<NSString *> * _Nonnull))completion;
|
|
372
|
+
/// Creates a voice clone from reference audio and saves it as a
|
|
373
|
+
/// scrambled .emd at <code>outputPath</code>.
|
|
374
|
+
/// <ul>
|
|
375
|
+
/// <li>
|
|
376
|
+
/// inputAudioPath: wav, mp3, m4a/aac, caf, aiff, flac, … The best ~6 s
|
|
377
|
+
/// of speech is used: silence trimmed, long pauses shortened, cut at a gap
|
|
378
|
+
/// between words (never inside a word).
|
|
379
|
+
/// </li>
|
|
380
|
+
/// <li>
|
|
381
|
+
/// outputPath: the .emd file to write (”.emd” is added when missing), or a
|
|
382
|
+
/// folder (-> <folder>/<input name>.emd). The file name is the voice name.
|
|
383
|
+
/// </li>
|
|
384
|
+
/// <li>
|
|
385
|
+
/// embedderPath: the language’s voice encoder, embedder_<code>.dm
|
|
386
|
+
/// (e.g. embedder_en.dm); it is unscrambled in memory only.
|
|
387
|
+
/// </li>
|
|
388
|
+
/// </ul>
|
|
389
|
+
/// Returns [“path”, “voice”, “seconds”, “warnings”, “report”]; “warnings” is
|
|
390
|
+
/// non-empty when less than 4 s of speech remained or the cut had to fall
|
|
391
|
+
/// inside speech. Put the file in the folder given to setExternalVoicesPath()
|
|
392
|
+
/// (and call that again) to use it.
|
|
393
|
+
+ (NSDictionary<NSString *, id> * _Nullable)createVoiceClone:(NSString * _Nonnull)inputAudioPath outputPath:(NSString * _Nonnull)outputPath embedderPath:(NSString * _Nonnull)embedderPath error:(NSError * _Nullable * _Nullable)error SWIFT_WARN_UNUSED_RESULT;
|
|
364
394
|
- (void)changeVoice:(NSString * _Nonnull)name completion:(void (^ _Nonnull)(NSInteger))completion;
|
|
365
395
|
@property (nonatomic, copy) void (^ _Nullable onLastUtteranceFinished)(void);
|
|
366
396
|
+ (BOOL)activateLicenseWithLicenseKey:(NSString * _Nonnull)licenseKey SWIFT_WARN_UNUSED_RESULT;
|