expo-speech 9.2.0 → 10.0.2
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/CHANGELOG.md +26 -0
- package/android/build.gradle +6 -11
- package/android/src/main/java/expo/modules/speech/LanguageUtils.kt +34 -0
- package/android/src/main/java/expo/modules/speech/SpeechModule.kt +191 -0
- package/android/src/main/java/expo/modules/speech/SpeechOptions.kt +58 -0
- package/android/src/main/java/expo/modules/speech/SpeechPackage.kt +8 -0
- package/build/ExponentSpeech.d.ts +1 -1
- package/build/ExponentSpeech.js +1 -1
- package/build/ExponentSpeech.js.map +1 -1
- package/build/ExponentSpeech.web.d.ts +1 -1
- package/build/ExponentSpeech.web.js +25 -5
- package/build/ExponentSpeech.web.js.map +1 -1
- package/build/Speech.d.ts +31 -2
- package/build/Speech.js +44 -2
- package/build/Speech.js.map +1 -1
- package/build/Speech.types.d.ts +44 -0
- package/build/Speech.types.js +4 -0
- package/build/Speech.types.js.map +1 -1
- package/ios/EXSpeech/EXSpeech.h +4 -4
- package/ios/EXSpeech/EXSpeech.m +25 -25
- package/ios/EXSpeech.podspec +3 -2
- package/package.json +6 -3
- package/src/Speech/ExponentSpeech.ts +1 -1
- package/src/Speech/ExponentSpeech.web.ts +33 -5
- package/src/Speech/Speech.ts +46 -4
- package/src/Speech/Speech.types.ts +48 -1
- package/android/src/main/java/expo/modules/speech/LanguageUtils.java +0 -48
- package/android/src/main/java/expo/modules/speech/SpeechModule.java +0 -238
- package/android/src/main/java/expo/modules/speech/SpeechPackage.java +0 -16
package/build/Speech.types.d.ts
CHANGED
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
export declare type SpeechEventCallback = (this: SpeechSynthesisUtterance, ev: SpeechSynthesisEvent) => any;
|
|
2
2
|
export declare type SpeechOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* The code of a language that should be used to read the `text`, refer to IETF BCP 47 to see
|
|
5
|
+
* valid codes.
|
|
6
|
+
*/
|
|
3
7
|
language?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Pitch of the voice to speak `text`. `1.0` is the normal pitch.
|
|
10
|
+
*/
|
|
4
11
|
pitch?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Rate of the voice to speak `text`. `1.0` is the normal rate.
|
|
14
|
+
*/
|
|
5
15
|
rate?: number;
|
|
16
|
+
/**
|
|
17
|
+
* A callback that is invoked when speaking starts.
|
|
18
|
+
*/
|
|
6
19
|
onStart?: () => void | SpeechEventCallback;
|
|
20
|
+
/**
|
|
21
|
+
* A callback that is invoked when speaking is stopped by calling `Speech.stop()`.
|
|
22
|
+
*/
|
|
7
23
|
onStopped?: () => void | SpeechEventCallback;
|
|
24
|
+
/**
|
|
25
|
+
* A callback that is invoked when speaking finishes.
|
|
26
|
+
*/
|
|
8
27
|
onDone?: () => void | SpeechEventCallback;
|
|
28
|
+
/**
|
|
29
|
+
* __(Android only).__ A callback that is invoked when an error occurred while speaking.
|
|
30
|
+
* @param error
|
|
31
|
+
*/
|
|
9
32
|
onError?: (error: Error) => void | SpeechEventCallback;
|
|
10
33
|
volume?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Voice identifier.
|
|
36
|
+
*/
|
|
11
37
|
voice?: string;
|
|
12
38
|
_voiceIndex?: number;
|
|
13
39
|
onBoundary?: SpeechEventCallback | null;
|
|
@@ -15,14 +41,32 @@ export declare type SpeechOptions = {
|
|
|
15
41
|
onPause?: SpeechEventCallback | null;
|
|
16
42
|
onResume?: SpeechEventCallback | null;
|
|
17
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Enum representing the voice quality.
|
|
46
|
+
*/
|
|
18
47
|
export declare enum VoiceQuality {
|
|
19
48
|
Default = "Default",
|
|
20
49
|
Enhanced = "Enhanced"
|
|
21
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Object describing the available voices on the device.
|
|
53
|
+
*/
|
|
22
54
|
export declare type Voice = {
|
|
55
|
+
/**
|
|
56
|
+
* Voice unique identifier.
|
|
57
|
+
*/
|
|
23
58
|
identifier: string;
|
|
59
|
+
/**
|
|
60
|
+
* Voice name.
|
|
61
|
+
*/
|
|
24
62
|
name: string;
|
|
63
|
+
/**
|
|
64
|
+
* Voice quality.
|
|
65
|
+
*/
|
|
25
66
|
quality: VoiceQuality;
|
|
67
|
+
/**
|
|
68
|
+
* Voice language.
|
|
69
|
+
*/
|
|
26
70
|
language: string;
|
|
27
71
|
};
|
|
28
72
|
export declare type WebVoice = Voice & {
|
package/build/Speech.types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Speech.types.js","sourceRoot":"","sources":["../src/Speech/Speech.types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Speech.types.js","sourceRoot":"","sources":["../src/Speech/Speech.types.ts"],"names":[],"mappings":"AA8CA,cAAc;AACd;;GAEG;AACH,MAAM,CAAN,IAAY,YAGX;AAHD,WAAY,YAAY;IACtB,mCAAmB,CAAA;IACnB,qCAAqB,CAAA;AACvB,CAAC,EAHW,YAAY,KAAZ,YAAY,QAGvB","sourcesContent":["export type SpeechEventCallback = (this: SpeechSynthesisUtterance, ev: SpeechSynthesisEvent) => any;\n\n// @needsAudit @docsMissing\nexport type SpeechOptions = {\n /**\n * The code of a language that should be used to read the `text`, refer to IETF BCP 47 to see\n * valid codes.\n */\n language?: string;\n /**\n * Pitch of the voice to speak `text`. `1.0` is the normal pitch.\n */\n pitch?: number;\n /**\n * Rate of the voice to speak `text`. `1.0` is the normal rate.\n */\n rate?: number;\n /**\n * A callback that is invoked when speaking starts.\n */\n onStart?: () => void | SpeechEventCallback;\n /**\n * A callback that is invoked when speaking is stopped by calling `Speech.stop()`.\n */\n onStopped?: () => void | SpeechEventCallback;\n /**\n * A callback that is invoked when speaking finishes.\n */\n onDone?: () => void | SpeechEventCallback;\n /**\n * __(Android only).__ A callback that is invoked when an error occurred while speaking.\n * @param error\n */\n onError?: (error: Error) => void | SpeechEventCallback;\n volume?: number;\n /**\n * Voice identifier.\n */\n voice?: string;\n _voiceIndex?: number;\n onBoundary?: SpeechEventCallback | null;\n onMark?: SpeechEventCallback | null;\n onPause?: SpeechEventCallback | null;\n onResume?: SpeechEventCallback | null;\n};\n\n// @needsAudit\n/**\n * Enum representing the voice quality.\n */\nexport enum VoiceQuality {\n Default = 'Default',\n Enhanced = 'Enhanced',\n}\n\n// @needsAudit\n/**\n * Object describing the available voices on the device.\n */\nexport type Voice = {\n /**\n * Voice unique identifier.\n */\n identifier: string;\n /**\n * Voice name.\n */\n name: string;\n /**\n * Voice quality.\n */\n quality: VoiceQuality;\n /**\n * Voice language.\n */\n language: string;\n};\n\n// @docsMissing\nexport type WebVoice = Voice & {\n isDefault: boolean;\n localService: boolean;\n name: string;\n voiceURI: string;\n};\n"]}
|
package/ios/EXSpeech/EXSpeech.h
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
2
|
|
|
3
|
-
#import <
|
|
4
|
-
#import <
|
|
5
|
-
#import <
|
|
3
|
+
#import <ExpoModulesCore/EXExportedModule.h>
|
|
4
|
+
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
|
|
5
|
+
#import <ExpoModulesCore/EXEventEmitter.h>
|
|
6
6
|
|
|
7
|
-
@interface EXSpeech :
|
|
7
|
+
@interface EXSpeech : EXExportedModule <EXEventEmitter, EXModuleRegistryConsumer>
|
|
8
8
|
|
|
9
9
|
@end
|
package/ios/EXSpeech/EXSpeech.m
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#import <AVFoundation/AVFoundation.h>
|
|
4
4
|
|
|
5
|
-
#import <
|
|
5
|
+
#import <ExpoModulesCore/EXEventEmitterService.h>
|
|
6
6
|
#import <EXSpeech/EXSpeech.h>
|
|
7
7
|
|
|
8
8
|
@interface EXSpeechUtteranceWithId : AVSpeechUtterance
|
|
@@ -32,15 +32,15 @@ static NSString *const INVALID_VOICE_ERROR_MSG = @"Cannot find voice with identi
|
|
|
32
32
|
@interface EXSpeech () <AVSpeechSynthesizerDelegate>
|
|
33
33
|
|
|
34
34
|
@property (nonatomic, strong) AVSpeechSynthesizer *synthesizer;
|
|
35
|
-
@property (nonatomic, weak)
|
|
35
|
+
@property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
|
|
36
36
|
|
|
37
37
|
@end
|
|
38
38
|
|
|
39
39
|
@implementation EXSpeech
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
EX_EXPORT_MODULE(ExponentSpeech)
|
|
42
42
|
|
|
43
|
-
- (void)setModuleRegistry:(
|
|
43
|
+
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
|
|
44
44
|
{
|
|
45
45
|
_moduleRegistry = moduleRegistry;
|
|
46
46
|
}
|
|
@@ -61,7 +61,7 @@ UM_EXPORT_MODULE(ExponentSpeech)
|
|
|
61
61
|
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
62
62
|
didStartSpeechUtterance:(AVSpeechUtterance *)utterance
|
|
63
63
|
{
|
|
64
|
-
id<
|
|
64
|
+
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
65
65
|
if (emitter != nil) {
|
|
66
66
|
[emitter sendEventWithName:@"Exponent.speakingStarted" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId }];
|
|
67
67
|
}
|
|
@@ -70,7 +70,7 @@ UM_EXPORT_MODULE(ExponentSpeech)
|
|
|
70
70
|
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
71
71
|
didCancelSpeechUtterance:(AVSpeechUtterance *)utterance
|
|
72
72
|
{
|
|
73
|
-
id<
|
|
73
|
+
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
74
74
|
if (emitter != nil) {
|
|
75
75
|
[emitter sendEventWithName:@"Exponent.speakingStopped" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId }];
|
|
76
76
|
}
|
|
@@ -79,19 +79,19 @@ UM_EXPORT_MODULE(ExponentSpeech)
|
|
|
79
79
|
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
80
80
|
didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
|
|
81
81
|
{
|
|
82
|
-
id<
|
|
82
|
+
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
83
83
|
if (emitter != nil) {
|
|
84
84
|
[emitter sendEventWithName:@"Exponent.speakingDone" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId }];
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
EX_EXPORT_METHOD_AS(speak,
|
|
90
90
|
speak:(nonnull NSString *)utteranceId
|
|
91
91
|
text:(nonnull NSString *)text
|
|
92
92
|
options:(NSDictionary *)options
|
|
93
|
-
resolver:(
|
|
94
|
-
rejecter:(
|
|
93
|
+
resolver:(EXPromiseResolveBlock)resolve
|
|
94
|
+
rejecter:(EXPromiseRejectBlock)reject) {
|
|
95
95
|
if (_synthesizer == nil) {
|
|
96
96
|
_synthesizer = [[AVSpeechSynthesizer alloc] init];
|
|
97
97
|
_synthesizer.delegate = self;
|
|
@@ -125,9 +125,9 @@ UM_EXPORT_METHOD_AS(speak,
|
|
|
125
125
|
resolve(nil);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
getVoices:(
|
|
130
|
-
rejecter:(
|
|
128
|
+
EX_EXPORT_METHOD_AS(getVoices,
|
|
129
|
+
getVoices:(EXPromiseResolveBlock)resolve
|
|
130
|
+
rejecter:(EXPromiseRejectBlock)reject) {
|
|
131
131
|
NSArray<AVSpeechSynthesisVoice *> *availableVoices = [AVSpeechSynthesisVoice speechVoices];
|
|
132
132
|
NSMutableArray<NSDictionary *> *availableVoicesResult = [NSMutableArray array];
|
|
133
133
|
for (AVSpeechSynthesisVoice* voice in availableVoices) {
|
|
@@ -146,30 +146,30 @@ UM_EXPORT_METHOD_AS(getVoices,
|
|
|
146
146
|
resolve([availableVoicesResult mutableCopy]);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
stop:(
|
|
151
|
-
rejecter:(
|
|
149
|
+
EX_EXPORT_METHOD_AS(stop,
|
|
150
|
+
stop:(EXPromiseResolveBlock)resolve
|
|
151
|
+
rejecter:(EXPromiseRejectBlock)reject) {
|
|
152
152
|
[_synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
|
153
153
|
resolve(nil);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
pause:(
|
|
158
|
-
rejecter:(
|
|
156
|
+
EX_EXPORT_METHOD_AS(pause,
|
|
157
|
+
pause:(EXPromiseResolveBlock)resolve
|
|
158
|
+
rejecter:(EXPromiseRejectBlock)reject) {
|
|
159
159
|
[_synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
|
160
160
|
resolve(nil);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
resume:(
|
|
165
|
-
rejecter:(
|
|
163
|
+
EX_EXPORT_METHOD_AS(resume,
|
|
164
|
+
resume:(EXPromiseResolveBlock)resolve
|
|
165
|
+
rejecter:(EXPromiseRejectBlock)reject) {
|
|
166
166
|
[_synthesizer continueSpeaking];
|
|
167
167
|
resolve(nil);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
isSpeaking:(
|
|
172
|
-
rejecter:(
|
|
170
|
+
EX_EXPORT_METHOD_AS(isSpeaking,
|
|
171
|
+
isSpeaking:(EXPromiseResolveBlock)resolve
|
|
172
|
+
rejecter:(EXPromiseRejectBlock)reject) {
|
|
173
173
|
resolve(@([_synthesizer isSpeaking]));
|
|
174
174
|
}
|
|
175
175
|
|
package/ios/EXSpeech.podspec
CHANGED
|
@@ -10,10 +10,11 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package['license']
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.homepage = package['homepage']
|
|
13
|
-
s.platform = :ios, '
|
|
13
|
+
s.platform = :ios, '12.0'
|
|
14
14
|
s.source = { git: 'https://github.com/expo/expo.git' }
|
|
15
|
+
s.static_framework = true
|
|
15
16
|
|
|
16
|
-
s.dependency '
|
|
17
|
+
s.dependency 'ExpoModulesCore'
|
|
17
18
|
|
|
18
19
|
if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
|
|
19
20
|
s.source_files = "#{s.name}/**/*.h"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-speech",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.2",
|
|
4
4
|
"description": "Provides text-to-speech functionality.",
|
|
5
5
|
"main": "build/Speech.js",
|
|
6
6
|
"types": "build/Speech.d.ts",
|
|
@@ -31,12 +31,15 @@
|
|
|
31
31
|
},
|
|
32
32
|
"author": "650 Industries, Inc.",
|
|
33
33
|
"license": "MIT",
|
|
34
|
-
"homepage": "https://docs.expo.
|
|
34
|
+
"homepage": "https://docs.expo.dev/versions/latest/sdk/speech/",
|
|
35
35
|
"jest": {
|
|
36
36
|
"preset": "expo-module-scripts/ios"
|
|
37
37
|
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"expo-modules-core": "~0.4.3"
|
|
40
|
+
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"expo-module-scripts": "^2.0.0"
|
|
40
43
|
},
|
|
41
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "d23e1ac491da96b51c25eb2533efcd56499ee287"
|
|
42
45
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { NativeModulesProxy } from '
|
|
1
|
+
import { NativeModulesProxy } from 'expo-modules-core';
|
|
2
2
|
export default NativeModulesProxy.ExponentSpeech;
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import { SyntheticPlatformEmitter, CodedError } from '
|
|
1
|
+
import { SyntheticPlatformEmitter, CodedError } from 'expo-modules-core';
|
|
2
2
|
|
|
3
3
|
import { SpeechOptions, WebVoice, VoiceQuality } from './Speech.types';
|
|
4
4
|
|
|
5
5
|
//https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance/text
|
|
6
6
|
const MAX_SPEECH_INPUT_LENGTH = 32767;
|
|
7
7
|
|
|
8
|
+
async function getVoices(): Promise<SpeechSynthesisVoice[]> {
|
|
9
|
+
return new Promise<SpeechSynthesisVoice[]>((resolve) => {
|
|
10
|
+
const voices = window.speechSynthesis.getVoices();
|
|
11
|
+
|
|
12
|
+
if (voices.length > 0) {
|
|
13
|
+
resolve(voices);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// when a page loads it takes some amount of time to populate the voices list
|
|
18
|
+
// see https://stackoverflow.com/a/52005323/4337317
|
|
19
|
+
window.speechSynthesis.onvoiceschanged = function () {
|
|
20
|
+
const voices = window.speechSynthesis.getVoices();
|
|
21
|
+
resolve(voices);
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
8
26
|
export default {
|
|
9
27
|
get name(): string {
|
|
10
28
|
return 'ExponentSpeech';
|
|
@@ -32,9 +50,19 @@ export default {
|
|
|
32
50
|
message.volume = options.volume;
|
|
33
51
|
}
|
|
34
52
|
if ('_voiceIndex' in options && options._voiceIndex != null) {
|
|
35
|
-
const voices =
|
|
53
|
+
const voices = await getVoices();
|
|
36
54
|
message.voice = voices[Math.min(voices.length - 1, Math.max(0, options._voiceIndex))];
|
|
37
55
|
}
|
|
56
|
+
if (typeof options.voice === 'string') {
|
|
57
|
+
const voices = await getVoices();
|
|
58
|
+
message.voice =
|
|
59
|
+
voices[
|
|
60
|
+
Math.max(
|
|
61
|
+
0,
|
|
62
|
+
voices.findIndex((voice) => voice.voiceURI === options.voice)
|
|
63
|
+
)
|
|
64
|
+
];
|
|
65
|
+
}
|
|
38
66
|
if (typeof options.onResume === 'function') {
|
|
39
67
|
message.onresume = options.onResume;
|
|
40
68
|
}
|
|
@@ -64,9 +92,9 @@ export default {
|
|
|
64
92
|
|
|
65
93
|
return message;
|
|
66
94
|
},
|
|
67
|
-
getVoices(): WebVoice[] {
|
|
68
|
-
const voices =
|
|
69
|
-
return voices.map(voice => ({
|
|
95
|
+
async getVoices(): Promise<WebVoice[]> {
|
|
96
|
+
const voices = await getVoices();
|
|
97
|
+
return voices.map((voice) => ({
|
|
70
98
|
identifier: voice.voiceURI,
|
|
71
99
|
quality: VoiceQuality.Default,
|
|
72
100
|
isDefault: voice.default,
|
package/src/Speech/Speech.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { UnavailabilityError } from '
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
2
|
import { NativeEventEmitter } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import ExponentSpeech from './ExponentSpeech';
|
|
5
|
-
import { SpeechOptions, SpeechEventCallback, VoiceQuality, Voice } from './Speech.types';
|
|
5
|
+
import { SpeechOptions, SpeechEventCallback, VoiceQuality, Voice, WebVoice } from './Speech.types';
|
|
6
6
|
|
|
7
7
|
const SpeechEventEmitter = ExponentSpeech && new NativeEventEmitter(ExponentSpeech);
|
|
8
8
|
|
|
9
|
-
export { SpeechOptions, SpeechEventCallback, VoiceQuality, Voice };
|
|
9
|
+
export { SpeechOptions, SpeechEventCallback, VoiceQuality, Voice, WebVoice };
|
|
10
10
|
|
|
11
11
|
const _CALLBACKS = {};
|
|
12
12
|
let _nextCallbackId = 1;
|
|
@@ -57,6 +57,13 @@ function _registerListenersIfNeeded() {
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// @needsAudit
|
|
61
|
+
/**
|
|
62
|
+
* Speak out loud the text given options. Calling this when another text is being spoken adds
|
|
63
|
+
* an utterance to queue.
|
|
64
|
+
* @param text The text to be spoken. Cannot be longer than [`Speech.maxSpeechInputLength`](#speechmaxspeechinputlength).
|
|
65
|
+
* @param options A `SpeechOptions` object.
|
|
66
|
+
*/
|
|
60
67
|
export function speak(text: string, options: SpeechOptions = {}) {
|
|
61
68
|
const id = _nextCallbackId++;
|
|
62
69
|
_CALLBACKS[id] = options;
|
|
@@ -64,6 +71,11 @@ export function speak(text: string, options: SpeechOptions = {}) {
|
|
|
64
71
|
ExponentSpeech.speak(String(id), text, options);
|
|
65
72
|
}
|
|
66
73
|
|
|
74
|
+
// @needsAudit
|
|
75
|
+
/**
|
|
76
|
+
* Returns list of all available voices.
|
|
77
|
+
* @return List of `Voice` objects.
|
|
78
|
+
*/
|
|
67
79
|
export async function getAvailableVoicesAsync(): Promise<Voice[]> {
|
|
68
80
|
if (!ExponentSpeech.getVoices) {
|
|
69
81
|
throw new UnavailabilityError('Speech', 'getVoices');
|
|
@@ -71,14 +83,28 @@ export async function getAvailableVoicesAsync(): Promise<Voice[]> {
|
|
|
71
83
|
return ExponentSpeech.getVoices();
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
//@needsAudit
|
|
87
|
+
/**
|
|
88
|
+
* Determine whether the Text-to-speech utility is currently speaking. Will return `true` if speaker
|
|
89
|
+
* is paused.
|
|
90
|
+
* @return Returns a Promise that fulfils with a boolean, `true` if speaking, `false` if not.
|
|
91
|
+
*/
|
|
74
92
|
export async function isSpeakingAsync(): Promise<boolean> {
|
|
75
93
|
return ExponentSpeech.isSpeaking();
|
|
76
94
|
}
|
|
77
95
|
|
|
96
|
+
// @needsAudit
|
|
97
|
+
/**
|
|
98
|
+
* Interrupts current speech and deletes all in queue.
|
|
99
|
+
*/
|
|
78
100
|
export async function stop(): Promise<void> {
|
|
79
101
|
return ExponentSpeech.stop();
|
|
80
102
|
}
|
|
81
103
|
|
|
104
|
+
// @needsAudit
|
|
105
|
+
/**
|
|
106
|
+
* Pauses current speech. This method is not available on Android.
|
|
107
|
+
*/
|
|
82
108
|
export async function pause(): Promise<void> {
|
|
83
109
|
if (!ExponentSpeech.pause) {
|
|
84
110
|
throw new UnavailabilityError('Speech', 'pause');
|
|
@@ -86,6 +112,11 @@ export async function pause(): Promise<void> {
|
|
|
86
112
|
return ExponentSpeech.pause();
|
|
87
113
|
}
|
|
88
114
|
|
|
115
|
+
// @needsAudit
|
|
116
|
+
/**
|
|
117
|
+
* Resumes speaking previously paused speech or does nothing if there's none. This method is not
|
|
118
|
+
* available on Android.
|
|
119
|
+
*/
|
|
89
120
|
export async function resume(): Promise<void> {
|
|
90
121
|
if (!ExponentSpeech.resume) {
|
|
91
122
|
throw new UnavailabilityError('Speech', 'resume');
|
|
@@ -95,7 +126,13 @@ export async function resume(): Promise<void> {
|
|
|
95
126
|
}
|
|
96
127
|
|
|
97
128
|
function setSpeakingListener(eventName, callback) {
|
|
98
|
-
|
|
129
|
+
// @ts-ignore: the EventEmitter interface has been changed in react-native@0.64.0
|
|
130
|
+
const listenerCount = SpeechEventEmitter.listenerCount
|
|
131
|
+
? // @ts-ignore: this is available since 0.64
|
|
132
|
+
SpeechEventEmitter.listenerCount(eventName)
|
|
133
|
+
: // @ts-ignore: this is available in older versions
|
|
134
|
+
SpeechEventEmitter.listeners(eventName).length;
|
|
135
|
+
if (listenerCount > 0) {
|
|
99
136
|
SpeechEventEmitter.removeAllListeners(eventName);
|
|
100
137
|
}
|
|
101
138
|
SpeechEventEmitter.addListener(eventName, callback);
|
|
@@ -105,4 +142,9 @@ function removeSpeakingListener(eventName) {
|
|
|
105
142
|
SpeechEventEmitter.removeAllListeners(eventName);
|
|
106
143
|
}
|
|
107
144
|
|
|
145
|
+
// @needsAudit
|
|
146
|
+
/**
|
|
147
|
+
* Maximum possible text length acceptable by `Speech.speak()` method. It is platform-dependent.
|
|
148
|
+
* On iOS, this returns `Number.MAX_VALUE`.
|
|
149
|
+
*/
|
|
108
150
|
export const maxSpeechInputLength: number = ExponentSpeech.maxSpeechInputLength || Number.MAX_VALUE;
|
|
@@ -1,15 +1,41 @@
|
|
|
1
1
|
export type SpeechEventCallback = (this: SpeechSynthesisUtterance, ev: SpeechSynthesisEvent) => any;
|
|
2
2
|
|
|
3
|
+
// @needsAudit @docsMissing
|
|
3
4
|
export type SpeechOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* The code of a language that should be used to read the `text`, refer to IETF BCP 47 to see
|
|
7
|
+
* valid codes.
|
|
8
|
+
*/
|
|
4
9
|
language?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Pitch of the voice to speak `text`. `1.0` is the normal pitch.
|
|
12
|
+
*/
|
|
5
13
|
pitch?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Rate of the voice to speak `text`. `1.0` is the normal rate.
|
|
16
|
+
*/
|
|
6
17
|
rate?: number;
|
|
18
|
+
/**
|
|
19
|
+
* A callback that is invoked when speaking starts.
|
|
20
|
+
*/
|
|
7
21
|
onStart?: () => void | SpeechEventCallback;
|
|
22
|
+
/**
|
|
23
|
+
* A callback that is invoked when speaking is stopped by calling `Speech.stop()`.
|
|
24
|
+
*/
|
|
8
25
|
onStopped?: () => void | SpeechEventCallback;
|
|
26
|
+
/**
|
|
27
|
+
* A callback that is invoked when speaking finishes.
|
|
28
|
+
*/
|
|
9
29
|
onDone?: () => void | SpeechEventCallback;
|
|
30
|
+
/**
|
|
31
|
+
* __(Android only).__ A callback that is invoked when an error occurred while speaking.
|
|
32
|
+
* @param error
|
|
33
|
+
*/
|
|
10
34
|
onError?: (error: Error) => void | SpeechEventCallback;
|
|
11
|
-
|
|
12
35
|
volume?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Voice identifier.
|
|
38
|
+
*/
|
|
13
39
|
voice?: string;
|
|
14
40
|
_voiceIndex?: number;
|
|
15
41
|
onBoundary?: SpeechEventCallback | null;
|
|
@@ -18,18 +44,39 @@ export type SpeechOptions = {
|
|
|
18
44
|
onResume?: SpeechEventCallback | null;
|
|
19
45
|
};
|
|
20
46
|
|
|
47
|
+
// @needsAudit
|
|
48
|
+
/**
|
|
49
|
+
* Enum representing the voice quality.
|
|
50
|
+
*/
|
|
21
51
|
export enum VoiceQuality {
|
|
22
52
|
Default = 'Default',
|
|
23
53
|
Enhanced = 'Enhanced',
|
|
24
54
|
}
|
|
25
55
|
|
|
56
|
+
// @needsAudit
|
|
57
|
+
/**
|
|
58
|
+
* Object describing the available voices on the device.
|
|
59
|
+
*/
|
|
26
60
|
export type Voice = {
|
|
61
|
+
/**
|
|
62
|
+
* Voice unique identifier.
|
|
63
|
+
*/
|
|
27
64
|
identifier: string;
|
|
65
|
+
/**
|
|
66
|
+
* Voice name.
|
|
67
|
+
*/
|
|
28
68
|
name: string;
|
|
69
|
+
/**
|
|
70
|
+
* Voice quality.
|
|
71
|
+
*/
|
|
29
72
|
quality: VoiceQuality;
|
|
73
|
+
/**
|
|
74
|
+
* Voice language.
|
|
75
|
+
*/
|
|
30
76
|
language: string;
|
|
31
77
|
};
|
|
32
78
|
|
|
79
|
+
// @docsMissing
|
|
33
80
|
export type WebVoice = Voice & {
|
|
34
81
|
isDefault: boolean;
|
|
35
82
|
localService: boolean;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
package expo.modules.speech;
|
|
2
|
-
|
|
3
|
-
import java.util.HashMap;
|
|
4
|
-
import java.util.Locale;
|
|
5
|
-
import java.util.Map;
|
|
6
|
-
|
|
7
|
-
// Lazy load the ISO codes into a Map then transform the codes to match other localization patterns in Expo
|
|
8
|
-
public class LanguageUtils {
|
|
9
|
-
|
|
10
|
-
private static Map<String, Locale> countryISOCodes;
|
|
11
|
-
|
|
12
|
-
private static Map<String, Locale> languageISOCodes;
|
|
13
|
-
|
|
14
|
-
private static String transformCountryISO(String code) {
|
|
15
|
-
if (countryISOCodes == null) {
|
|
16
|
-
String[] countries = Locale.getISOCountries();
|
|
17
|
-
countryISOCodes = new HashMap<>(countries.length);
|
|
18
|
-
for (String country : countries) {
|
|
19
|
-
Locale locale = new Locale("", country);
|
|
20
|
-
countryISOCodes.put(locale.getISO3Country().toUpperCase(), locale);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return countryISOCodes.get(code).getCountry();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
private static String transformLanguageISO(String code) {
|
|
27
|
-
if (languageISOCodes == null) {
|
|
28
|
-
String[] languages = Locale.getISOLanguages();
|
|
29
|
-
languageISOCodes = new HashMap<>(languages.length);
|
|
30
|
-
for (String language : languages) {
|
|
31
|
-
Locale locale = new Locale(language);
|
|
32
|
-
languageISOCodes.put(locale.getISO3Language(), locale);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return languageISOCodes.get(code).getLanguage();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static String getISOCode(Locale locale) {
|
|
39
|
-
String language = transformLanguageISO(locale.getISO3Language());
|
|
40
|
-
String country = locale.getISO3Country();
|
|
41
|
-
if (!country.equals("")) {
|
|
42
|
-
String countryCode = transformCountryISO(country);
|
|
43
|
-
language += "-" + countryCode;
|
|
44
|
-
}
|
|
45
|
-
return language;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|