expo-speech 11.1.1 → 11.3.0
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 +12 -0
- package/README.md +10 -3
- package/android/build.gradle +8 -10
- package/android/src/main/AndroidManifest.xml +1 -3
- package/android/src/main/java/expo/modules/speech/SpeechModule.kt +1 -1
- package/build/ExponentSpeech.d.ts +1 -1
- package/build/ExponentSpeech.js +2 -2
- package/build/ExponentSpeech.js.map +1 -1
- package/expo-module.config.json +7 -0
- package/ios/{EXSpeech.podspec → ExpoSpeech.podspec} +4 -3
- package/ios/SpeechDelegate.swift +32 -0
- package/ios/SpeechExceptions.swift +7 -0
- package/ios/SpeechModule.swift +138 -0
- package/ios/SpeechOptions.swift +23 -0
- package/package.json +2 -2
- package/src/Speech/ExponentSpeech.ts +2 -2
- package/ios/EXSpeech/EXSpeech.h +0 -9
- package/ios/EXSpeech/EXSpeech.m +0 -186
- package/unimodule.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 11.3.0 — 2023-06-21
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
|
|
18
|
+
|
|
19
|
+
## 11.2.0 — 2023-05-08
|
|
20
|
+
|
|
21
|
+
### 🎉 New features
|
|
22
|
+
|
|
23
|
+
- Migrated iOS codebase to use Expo modules API. ([#21814](https://github.com/expo/expo/pull/21814) by [@alanjhughes](https://github.com/alanjhughes))
|
|
24
|
+
|
|
13
25
|
## 11.1.1 — 2023-02-09
|
|
14
26
|
|
|
15
27
|
_This version does not introduce any user-facing changes._
|
package/README.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
<p>
|
|
2
|
+
<a href="https://docs.expo.dev/versions/latest/sdk/speech/">
|
|
3
|
+
<img
|
|
4
|
+
src="../../.github/resources/expo-speech.svg"
|
|
5
|
+
alt="expo-speech"
|
|
6
|
+
height="64" />
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
10
|
Provides text-to-speech functionality.
|
|
4
11
|
|
|
@@ -9,7 +16,7 @@ Provides text-to-speech functionality.
|
|
|
9
16
|
|
|
10
17
|
# Installation in managed Expo projects
|
|
11
18
|
|
|
12
|
-
For [managed](https://docs.expo.dev/
|
|
19
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/speech/).
|
|
13
20
|
|
|
14
21
|
# Installation in bare React Native projects
|
|
15
22
|
|
|
@@ -18,7 +25,7 @@ For bare React Native projects, you must ensure that you have [installed and con
|
|
|
18
25
|
### Add the package to your npm dependencies
|
|
19
26
|
|
|
20
27
|
```
|
|
21
|
-
expo install expo-speech
|
|
28
|
+
npx expo install expo-speech
|
|
22
29
|
```
|
|
23
30
|
|
|
24
31
|
### Configure for iOS
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '11.
|
|
6
|
+
version = '11.3.0'
|
|
7
7
|
|
|
8
8
|
buildscript {
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
@@ -35,19 +35,11 @@ buildscript {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Creating sources with comments
|
|
39
|
-
task androidSourcesJar(type: Jar) {
|
|
40
|
-
classifier = 'sources'
|
|
41
|
-
from android.sourceSets.main.java.srcDirs
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
afterEvaluate {
|
|
45
39
|
publishing {
|
|
46
40
|
publications {
|
|
47
41
|
release(MavenPublication) {
|
|
48
42
|
from components.release
|
|
49
|
-
// Add additional sourcesJar to artifacts
|
|
50
|
-
artifact(androidSourcesJar)
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
45
|
repositories {
|
|
@@ -70,15 +62,21 @@ android {
|
|
|
70
62
|
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
|
71
63
|
}
|
|
72
64
|
|
|
65
|
+
namespace "expo.modules.speech"
|
|
73
66
|
defaultConfig {
|
|
74
67
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
75
68
|
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
76
69
|
versionCode 18
|
|
77
|
-
versionName "11.
|
|
70
|
+
versionName "11.3.0"
|
|
78
71
|
}
|
|
79
72
|
lintOptions {
|
|
80
73
|
abortOnError false
|
|
81
74
|
}
|
|
75
|
+
publishing {
|
|
76
|
+
singleVariant("release") {
|
|
77
|
+
withSourcesJar()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
dependencies {
|
|
@@ -27,7 +27,7 @@ class SpeechModule(
|
|
|
27
27
|
private val delayedUtterances: Queue<Utterance> = ArrayDeque()
|
|
28
28
|
|
|
29
29
|
// Module basic definitions
|
|
30
|
-
override fun getName() = "
|
|
30
|
+
override fun getName() = "ExpoSpeech"
|
|
31
31
|
override fun getConstants() = mapOf(
|
|
32
32
|
"maxSpeechInputLength" to TextToSpeech.getMaxSpeechInputLength()
|
|
33
33
|
)
|
package/build/ExponentSpeech.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
export default requireNativeModule('ExpoSpeech');
|
|
3
3
|
//# sourceMappingURL=ExponentSpeech.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExponentSpeech.js","sourceRoot":"","sources":["../src/Speech/ExponentSpeech.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ExponentSpeech.js","sourceRoot":"","sources":["../src/Speech/ExponentSpeech.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,eAAe,mBAAmB,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\nexport default requireNativeModule('ExpoSpeech');\n"]}
|
|
@@ -3,7 +3,7 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = '
|
|
6
|
+
s.name = 'ExpoSpeech'
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = package['description']
|
|
9
9
|
s.description = package['description']
|
|
@@ -11,15 +11,16 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.homepage = package['homepage']
|
|
13
13
|
s.platform = :ios, '13.0'
|
|
14
|
+
s.swift_version = '5.4'
|
|
14
15
|
s.source = { git: 'https://github.com/expo/expo.git' }
|
|
15
16
|
s.static_framework = true
|
|
16
17
|
|
|
17
18
|
s.dependency 'ExpoModulesCore'
|
|
18
19
|
|
|
19
20
|
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')
|
|
20
|
-
s.source_files = "
|
|
21
|
+
s.source_files = "**/*.h"
|
|
21
22
|
s.vendored_frameworks = "#{s.name}.xcframework"
|
|
22
23
|
else
|
|
23
|
-
s.source_files = "
|
|
24
|
+
s.source_files = "**/*.{h,m,swift}"
|
|
24
25
|
end
|
|
25
26
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Speech
|
|
2
|
+
|
|
3
|
+
protocol SpeechResultHadler {
|
|
4
|
+
func didStart(utterance: AVSpeechUtterance)
|
|
5
|
+
func willSpeak(characterRange: NSRange, utterance: AVSpeechUtterance)
|
|
6
|
+
func didCancel(utterance: AVSpeechUtterance)
|
|
7
|
+
func didFinish(utterance: AVSpeechUtterance)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class SpeechDelegate: NSObject, AVSpeechSynthesizerDelegate {
|
|
11
|
+
let resultHandler: SpeechResultHadler
|
|
12
|
+
|
|
13
|
+
init(resultHandler: SpeechResultHadler) {
|
|
14
|
+
self.resultHandler = resultHandler
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
|
|
18
|
+
resultHandler.didStart(utterance: utterance)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
|
|
22
|
+
resultHandler.willSpeak(characterRange: characterRange, utterance: utterance)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
|
|
26
|
+
resultHandler.didCancel(utterance: utterance)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
|
|
30
|
+
resultHandler.didFinish(utterance: utterance)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
let SPEAKING_STARTED = "Exponent.speakingStarted"
|
|
4
|
+
let SPEAKING_WILL_SAY_NEXT_STRING = "Exponent.speakingWillSayNextString"
|
|
5
|
+
let SPEAKING_DONE = "Exponent.speakingDone"
|
|
6
|
+
let SPEAKING_STOPPED = "Exponent.speakingStopped"
|
|
7
|
+
let SPEAKING_ERROR = "Exponent.speakingError"
|
|
8
|
+
|
|
9
|
+
public final class SpeechModule: Module, SpeechResultHadler {
|
|
10
|
+
private var synthesizer = AVSpeechSynthesizer()
|
|
11
|
+
private var delegate: SpeechDelegate?
|
|
12
|
+
|
|
13
|
+
public func definition() -> ModuleDefinition {
|
|
14
|
+
Name("ExpoSpeech")
|
|
15
|
+
|
|
16
|
+
OnCreate {
|
|
17
|
+
delegate = SpeechDelegate(resultHandler: self)
|
|
18
|
+
synthesizer.delegate = delegate
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Events([
|
|
22
|
+
SPEAKING_STARTED,
|
|
23
|
+
SPEAKING_WILL_SAY_NEXT_STRING,
|
|
24
|
+
SPEAKING_DONE,
|
|
25
|
+
SPEAKING_STOPPED,
|
|
26
|
+
SPEAKING_ERROR
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
AsyncFunction("speak") { (utteranceId: String, text: String, options: SpeechOptions) in
|
|
30
|
+
let utterance = ExpoSpeechUtterance(id: utteranceId, text: text)
|
|
31
|
+
|
|
32
|
+
if let language = options.language {
|
|
33
|
+
utterance.voice = AVSpeechSynthesisVoice(language: language)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if let voice = options.voice {
|
|
37
|
+
utterance.voice = AVSpeechSynthesisVoice(identifier: voice)
|
|
38
|
+
|
|
39
|
+
guard utterance.voice != nil else {
|
|
40
|
+
throw InvalidVoiceException(voice)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if let pitch = options.pitch {
|
|
45
|
+
utterance.pitchMultiplier = Float(pitch)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if let rate = options.rate {
|
|
49
|
+
utterance.rate = Float(rate) * AVSpeechUtteranceDefaultSpeechRate
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
synthesizer.speak(utterance)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
AsyncFunction("getVoices") { () -> [VoiceInfo] in
|
|
56
|
+
let voices = AVSpeechSynthesisVoice.speechVoices()
|
|
57
|
+
|
|
58
|
+
let availableVoicesResult = voices.map { voice in
|
|
59
|
+
VoiceInfo(
|
|
60
|
+
identifier: voice.identifier,
|
|
61
|
+
name: voice.name,
|
|
62
|
+
quality: voice.quality == .enhanced ? "Enhanced" : "Default",
|
|
63
|
+
language: voice.language
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return availableVoicesResult
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
AsyncFunction("stop") {
|
|
71
|
+
synthesizer.stopSpeaking(at: .immediate)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
AsyncFunction("pause") {
|
|
75
|
+
synthesizer.pauseSpeaking(at: .immediate)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
AsyncFunction("resume") {
|
|
79
|
+
synthesizer.continueSpeaking()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
AsyncFunction("isSpeaking") {
|
|
83
|
+
return synthesizer.isSpeaking
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func didStart(utterance: AVSpeechUtterance) {
|
|
88
|
+
guard let utterance = utterance as? ExpoSpeechUtterance else {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
sendEvent(SPEAKING_STARTED, [
|
|
92
|
+
"id": utterance.id
|
|
93
|
+
])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func willSpeak(characterRange: NSRange, utterance: AVSpeechUtterance) {
|
|
97
|
+
guard let utterance = utterance as? ExpoSpeechUtterance else {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
sendEvent(SPEAKING_WILL_SAY_NEXT_STRING, [
|
|
101
|
+
"id": utterance.id,
|
|
102
|
+
"charIndex": characterRange.location,
|
|
103
|
+
"charLength": characterRange.length
|
|
104
|
+
])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func didCancel(utterance: AVSpeechUtterance) {
|
|
108
|
+
guard let utterance = utterance as? ExpoSpeechUtterance else {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
sendEvent(SPEAKING_STOPPED, [
|
|
112
|
+
"id": utterance.id
|
|
113
|
+
])
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func didFinish(utterance: AVSpeechUtterance) {
|
|
117
|
+
guard let utterance = utterance as? ExpoSpeechUtterance else {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
sendEvent(SPEAKING_DONE, [
|
|
121
|
+
"id": utterance.id
|
|
122
|
+
])
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
internal class ExpoSpeechUtterance: AVSpeechUtterance {
|
|
127
|
+
let id: String
|
|
128
|
+
|
|
129
|
+
init(id: String, text: String) {
|
|
130
|
+
self.id = id
|
|
131
|
+
super.init(string: text)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@available(*, unavailable)
|
|
135
|
+
required init?(coder: NSCoder) {
|
|
136
|
+
fatalError("Not implemented")
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
struct SpeechOptions: Record {
|
|
4
|
+
@Field
|
|
5
|
+
var language: String?
|
|
6
|
+
@Field
|
|
7
|
+
var pitch: Double?
|
|
8
|
+
@Field
|
|
9
|
+
var rate: Double?
|
|
10
|
+
@Field
|
|
11
|
+
var voice: String?
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
struct VoiceInfo: Record {
|
|
15
|
+
@Field
|
|
16
|
+
var identifier: String? = nil
|
|
17
|
+
@Field
|
|
18
|
+
var name: String? = nil
|
|
19
|
+
@Field
|
|
20
|
+
var quality: String? = nil
|
|
21
|
+
@Field
|
|
22
|
+
var language: String? = nil
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-speech",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.0",
|
|
4
4
|
"description": "Provides text-to-speech functionality.",
|
|
5
5
|
"main": "build/Speech.js",
|
|
6
6
|
"types": "build/Speech.d.ts",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"expo": "*"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "fa5ecca8251986b9f197cc14074eec0ab6dfb6db"
|
|
46
46
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default
|
|
1
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
2
|
+
export default requireNativeModule('ExpoSpeech');
|
package/ios/EXSpeech/EXSpeech.h
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
-
|
|
3
|
-
#import <ExpoModulesCore/EXExportedModule.h>
|
|
4
|
-
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
|
|
5
|
-
#import <ExpoModulesCore/EXEventEmitter.h>
|
|
6
|
-
|
|
7
|
-
@interface EXSpeech : EXExportedModule <EXEventEmitter, EXModuleRegistryConsumer>
|
|
8
|
-
|
|
9
|
-
@end
|
package/ios/EXSpeech/EXSpeech.m
DELETED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
-
|
|
3
|
-
#import <AVFoundation/AVFoundation.h>
|
|
4
|
-
|
|
5
|
-
#import <ExpoModulesCore/EXEventEmitterService.h>
|
|
6
|
-
#import <EXSpeech/EXSpeech.h>
|
|
7
|
-
|
|
8
|
-
@interface EXSpeechUtteranceWithId : AVSpeechUtterance
|
|
9
|
-
|
|
10
|
-
@property (nonatomic) NSString* utteranceId;
|
|
11
|
-
|
|
12
|
-
- (instancetype)initWithString:(NSString *)string utteranceId:(NSString *)utteranceId;
|
|
13
|
-
|
|
14
|
-
@end
|
|
15
|
-
|
|
16
|
-
@implementation EXSpeechUtteranceWithId
|
|
17
|
-
|
|
18
|
-
static NSString *const INVALID_VOICE_ERROR_CODE = @"INVALID_VOICE_IDENTIFIER";
|
|
19
|
-
static NSString *const INVALID_VOICE_ERROR_MSG = @"Cannot find voice with identifier: %@!";
|
|
20
|
-
|
|
21
|
-
- (instancetype)initWithString:(NSString *)string utteranceId:(NSString *)utteranceId
|
|
22
|
-
{
|
|
23
|
-
self = [super initWithString:string];
|
|
24
|
-
if (self) {
|
|
25
|
-
_utteranceId = utteranceId;
|
|
26
|
-
}
|
|
27
|
-
return self;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@end
|
|
31
|
-
|
|
32
|
-
@interface EXSpeech () <AVSpeechSynthesizerDelegate>
|
|
33
|
-
|
|
34
|
-
@property (nonatomic, strong) AVSpeechSynthesizer *synthesizer;
|
|
35
|
-
@property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
|
|
36
|
-
|
|
37
|
-
@end
|
|
38
|
-
|
|
39
|
-
@implementation EXSpeech
|
|
40
|
-
|
|
41
|
-
EX_EXPORT_MODULE(ExponentSpeech)
|
|
42
|
-
|
|
43
|
-
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
|
|
44
|
-
{
|
|
45
|
-
_moduleRegistry = moduleRegistry;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
- (NSArray<NSString *> *)supportedEvents
|
|
49
|
-
{
|
|
50
|
-
return @[@"Exponent.speakingStarted", @"Exponent.speakingWillSayNextString", @"Exponent.speakingDone", @"Exponent.speakingStopped", @"Exponent.speakingError"];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
- (void)startObserving {
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- (void)stopObserving {
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
62
|
-
didStartSpeechUtterance:(AVSpeechUtterance *)utterance
|
|
63
|
-
{
|
|
64
|
-
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
65
|
-
if (emitter != nil) {
|
|
66
|
-
[emitter sendEventWithName:@"Exponent.speakingStarted" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId }];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
71
|
-
willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(nonnull AVSpeechUtterance *)utterance
|
|
72
|
-
{
|
|
73
|
-
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
74
|
-
if (emitter != nil) {
|
|
75
|
-
[emitter sendEventWithName:@"Exponent.speakingWillSayNextString" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId, @"charIndex": @(characterRange.location), @"charLength": @(characterRange.length) }];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
80
|
-
didCancelSpeechUtterance:(AVSpeechUtterance *)utterance
|
|
81
|
-
{
|
|
82
|
-
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
83
|
-
if (emitter != nil) {
|
|
84
|
-
[emitter sendEventWithName:@"Exponent.speakingStopped" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId }];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer
|
|
89
|
-
didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
|
|
90
|
-
{
|
|
91
|
-
id<EXEventEmitterService> emitter = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
|
|
92
|
-
if (emitter != nil) {
|
|
93
|
-
[emitter sendEventWithName:@"Exponent.speakingDone" body:@{ @"id": ((EXSpeechUtteranceWithId *) utterance).utteranceId }];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
EX_EXPORT_METHOD_AS(speak,
|
|
99
|
-
speak:(nonnull NSString *)utteranceId
|
|
100
|
-
text:(nonnull NSString *)text
|
|
101
|
-
options:(NSDictionary *)options
|
|
102
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
103
|
-
rejecter:(EXPromiseRejectBlock)reject) {
|
|
104
|
-
if (_synthesizer == nil) {
|
|
105
|
-
_synthesizer = [[AVSpeechSynthesizer alloc] init];
|
|
106
|
-
_synthesizer.delegate = self;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
AVSpeechUtterance *utterance = [[EXSpeechUtteranceWithId alloc] initWithString:text utteranceId:utteranceId];
|
|
110
|
-
|
|
111
|
-
NSString *language = options[@"language"];
|
|
112
|
-
NSString *voice = options[@"voice"];
|
|
113
|
-
NSNumber *pitch = options[@"pitch"];
|
|
114
|
-
NSNumber *rate = options[@"rate"];
|
|
115
|
-
|
|
116
|
-
if (language != nil) {
|
|
117
|
-
utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:language];
|
|
118
|
-
}
|
|
119
|
-
if (voice != nil) {
|
|
120
|
-
utterance.voice = [AVSpeechSynthesisVoice voiceWithIdentifier:voice];
|
|
121
|
-
if (utterance.voice == nil) {
|
|
122
|
-
reject(INVALID_VOICE_ERROR_CODE, [NSString stringWithFormat:INVALID_VOICE_ERROR_MSG, voice], nil);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (pitch != nil) {
|
|
127
|
-
utterance.pitchMultiplier = [pitch floatValue];
|
|
128
|
-
}
|
|
129
|
-
if (rate != nil) {
|
|
130
|
-
utterance.rate = [rate floatValue] * AVSpeechUtteranceDefaultSpeechRate;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
[_synthesizer speakUtterance:utterance];
|
|
134
|
-
resolve(nil);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
EX_EXPORT_METHOD_AS(getVoices,
|
|
138
|
-
getVoices:(EXPromiseResolveBlock)resolve
|
|
139
|
-
rejecter:(EXPromiseRejectBlock)reject) {
|
|
140
|
-
NSArray<AVSpeechSynthesisVoice *> *availableVoices = [AVSpeechSynthesisVoice speechVoices];
|
|
141
|
-
NSMutableArray<NSDictionary *> *availableVoicesResult = [NSMutableArray array];
|
|
142
|
-
for (AVSpeechSynthesisVoice* voice in availableVoices) {
|
|
143
|
-
NSString *quality = @"Default";
|
|
144
|
-
if (voice.quality == AVSpeechSynthesisVoiceQualityEnhanced) {
|
|
145
|
-
quality = @"Enhanced";
|
|
146
|
-
}
|
|
147
|
-
NSDictionary *voiceInfo = @{
|
|
148
|
-
@"identifier" : voice.identifier,
|
|
149
|
-
@"name" : voice.name,
|
|
150
|
-
@"quality" : quality,
|
|
151
|
-
@"language" : voice.language
|
|
152
|
-
};
|
|
153
|
-
[availableVoicesResult addObject:voiceInfo];
|
|
154
|
-
}
|
|
155
|
-
resolve([availableVoicesResult mutableCopy]);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
EX_EXPORT_METHOD_AS(stop,
|
|
159
|
-
stop:(EXPromiseResolveBlock)resolve
|
|
160
|
-
rejecter:(EXPromiseRejectBlock)reject) {
|
|
161
|
-
[_synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
|
162
|
-
resolve(nil);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
EX_EXPORT_METHOD_AS(pause,
|
|
166
|
-
pause:(EXPromiseResolveBlock)resolve
|
|
167
|
-
rejecter:(EXPromiseRejectBlock)reject) {
|
|
168
|
-
[_synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
|
|
169
|
-
resolve(nil);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
EX_EXPORT_METHOD_AS(resume,
|
|
173
|
-
resume:(EXPromiseResolveBlock)resolve
|
|
174
|
-
rejecter:(EXPromiseRejectBlock)reject) {
|
|
175
|
-
[_synthesizer continueSpeaking];
|
|
176
|
-
resolve(nil);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
EX_EXPORT_METHOD_AS(isSpeaking,
|
|
180
|
-
isSpeaking:(EXPromiseResolveBlock)resolve
|
|
181
|
-
rejecter:(EXPromiseRejectBlock)reject) {
|
|
182
|
-
resolve(@([_synthesizer isSpeaking]));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
@end
|
|
186
|
-
|
package/unimodule.json
DELETED