@focus8/expo-acapela-tts 0.1.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/android/build.gradle +43 -0
- package/android/libs/acattsandroid-sdk-library-release.aar +0 -0
- package/android/src/main/java/expo/modules/acapelatts/ExpoAcapelaTtsModule.kt +272 -0
- package/android/src/main/jniLibs/arm64-v8a/libacattsandroid.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libacattsandroid.so +0 -0
- package/android/src/main/jniLibs/x86/libacattsandroid.so +0 -0
- package/android/src/main/jniLibs/x86_64/libacattsandroid.so +0 -0
- package/build/ExpoAcapelaTts.types.d.ts +19 -0
- package/build/ExpoAcapelaTts.types.d.ts.map +1 -0
- package/build/ExpoAcapelaTts.types.js +2 -0
- package/build/ExpoAcapelaTts.types.js.map +1 -0
- package/build/ExpoAcapelaTtsModule.android.d.ts +18 -0
- package/build/ExpoAcapelaTtsModule.android.d.ts.map +1 -0
- package/build/ExpoAcapelaTtsModule.android.js +3 -0
- package/build/ExpoAcapelaTtsModule.android.js.map +1 -0
- package/build/ExpoAcapelaTtsModule.d.ts +19 -0
- package/build/ExpoAcapelaTtsModule.d.ts.map +1 -0
- package/build/ExpoAcapelaTtsModule.js +31 -0
- package/build/ExpoAcapelaTtsModule.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/package.json +48 -0
- package/src/ExpoAcapelaTts.types.ts +16 -0
- package/src/ExpoAcapelaTtsModule.android.ts +22 -0
- package/src/ExpoAcapelaTtsModule.ts +30 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
|
|
3
|
+
group = 'expo.modules.acapelatts'
|
|
4
|
+
version = '0.1.0'
|
|
5
|
+
|
|
6
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
|
+
apply from: expoModulesCorePlugin
|
|
8
|
+
applyKotlinExpoModulesCorePlugin()
|
|
9
|
+
useCoreDependencies()
|
|
10
|
+
useExpoPublishing()
|
|
11
|
+
|
|
12
|
+
def useManagedAndroidSdkVersions = false
|
|
13
|
+
if (useManagedAndroidSdkVersions) {
|
|
14
|
+
useDefaultAndroidSdkVersions()
|
|
15
|
+
} else {
|
|
16
|
+
buildscript {
|
|
17
|
+
ext.safeExtGet = { prop, fallback ->
|
|
18
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
project.android {
|
|
22
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
25
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
android {
|
|
31
|
+
namespace "expo.modules.acapelatts"
|
|
32
|
+
defaultConfig {
|
|
33
|
+
versionCode 1
|
|
34
|
+
versionName "0.1.0"
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dependencies {
|
|
42
|
+
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
|
43
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
package expo.modules.acapelatts
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.acapelagroup.android.tts.acattsandroid
|
|
5
|
+
import com.acapelagroup.android.tts.acattsandroid.iTTSEventsCallback
|
|
6
|
+
import expo.modules.kotlin.modules.Module
|
|
7
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
8
|
+
import java.io.File
|
|
9
|
+
|
|
10
|
+
class ExpoAcapelaTtsModule : Module(), iTTSEventsCallback {
|
|
11
|
+
|
|
12
|
+
private var tts: acattsandroid? = null
|
|
13
|
+
private var isInitialized = false
|
|
14
|
+
private var currentVoice: String? = null
|
|
15
|
+
|
|
16
|
+
companion object {
|
|
17
|
+
private const val TAG = "ExpoAcapelaTts"
|
|
18
|
+
private const val VOICES_PATH = "/system/media/voices"
|
|
19
|
+
|
|
20
|
+
private const val LICENSE_USER_ID = 0x34307771L
|
|
21
|
+
private const val LICENSE_PASSWORD = 0x0008d44fL
|
|
22
|
+
private const val LICENSE_KEY = "\"1976 0 qw04 #EVALUATION#Focus8\"\n" +
|
|
23
|
+
"R%NUNVGz28KZdGZestTLeuatIwSRJoV8wn33qep6qR##\n" +
|
|
24
|
+
"VOO8rYtC9QJpPfoX!8wlLrI@JXk3KnFJ%fhyL4AlArB!xoRq\n" +
|
|
25
|
+
"UCNfz!@aiT9Sp88aLHnq\n"
|
|
26
|
+
|
|
27
|
+
private var nativeLibLoaded = false
|
|
28
|
+
|
|
29
|
+
init {
|
|
30
|
+
try {
|
|
31
|
+
System.loadLibrary("acattsandroid")
|
|
32
|
+
nativeLibLoaded = true
|
|
33
|
+
Log.i(TAG, "Native library loaded")
|
|
34
|
+
} catch (e: UnsatisfiedLinkError) {
|
|
35
|
+
Log.w(TAG, "Failed to load native library: ${e.message}")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun definition() = ModuleDefinition {
|
|
41
|
+
|
|
42
|
+
Name("ExpoAcapelaTts")
|
|
43
|
+
|
|
44
|
+
Events("onSpeechStart", "onSpeechEnd", "onError")
|
|
45
|
+
|
|
46
|
+
Function("isAvailable") {
|
|
47
|
+
if (!nativeLibLoaded) {
|
|
48
|
+
return@Function false
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
val voicesDir = File(VOICES_PATH)
|
|
52
|
+
val available = voicesDir.exists() && voicesDir.isDirectory &&
|
|
53
|
+
(voicesDir.list()?.isNotEmpty() == true)
|
|
54
|
+
logInfo("isAvailable: $available (path: $VOICES_PATH)")
|
|
55
|
+
available
|
|
56
|
+
} catch (e: Exception) {
|
|
57
|
+
logError("isAvailable check failed", e)
|
|
58
|
+
false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
AsyncFunction("initializeAsync") {
|
|
63
|
+
if (!nativeLibLoaded) {
|
|
64
|
+
logWarning("Cannot initialize: native library not loaded")
|
|
65
|
+
return@AsyncFunction false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isInitialized && tts != null) {
|
|
69
|
+
logInfo("Already initialized")
|
|
70
|
+
return@AsyncFunction true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
val context = appContext.reactContext ?: run {
|
|
75
|
+
logWarning("ReactContext not available")
|
|
76
|
+
return@AsyncFunction false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
tts = acattsandroid(context, this@ExpoAcapelaTtsModule, null)
|
|
80
|
+
tts!!.setLog(true)
|
|
81
|
+
tts!!.setLicense(LICENSE_USER_ID, LICENSE_PASSWORD, LICENSE_KEY)
|
|
82
|
+
|
|
83
|
+
val version = tts!!.version
|
|
84
|
+
logInfo("Initialized, SDK version: $version")
|
|
85
|
+
|
|
86
|
+
isInitialized = true
|
|
87
|
+
true
|
|
88
|
+
} catch (e: Exception) {
|
|
89
|
+
logError("Failed to initialize", e)
|
|
90
|
+
tts = null
|
|
91
|
+
isInitialized = false
|
|
92
|
+
false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
AsyncFunction("getVoicesAsync") {
|
|
97
|
+
if (tts == null) {
|
|
98
|
+
logWarning("getVoicesAsync: TTS not initialized")
|
|
99
|
+
return@AsyncFunction emptyList<Map<String, Any>>()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
val voiceDirPaths = arrayOf(VOICES_PATH)
|
|
104
|
+
val voicesList = tts!!.getVoicesList(voiceDirPaths) ?: emptyArray()
|
|
105
|
+
logInfo("Found ${voicesList.size} voices")
|
|
106
|
+
|
|
107
|
+
val voices = voicesList.map { voiceName ->
|
|
108
|
+
val info = tts!!.getVoiceInfo(voiceName) ?: emptyMap()
|
|
109
|
+
val genderValue = info["gender"] ?: "0"
|
|
110
|
+
mapOf(
|
|
111
|
+
"name" to voiceName,
|
|
112
|
+
"locale" to (info["locale"] ?: ""),
|
|
113
|
+
"language" to (info["language"] ?: ""),
|
|
114
|
+
"speaker" to (info["name"]?.let { extractSpeakerName(it) } ?: ""),
|
|
115
|
+
"gender" to if (genderValue == "1") "female" else "male",
|
|
116
|
+
"quality" to (info["quality"] ?: ""),
|
|
117
|
+
"age" to (info["age"] ?: ""),
|
|
118
|
+
"frequency" to (info["frequency"] ?: "")
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Reload the current voice since getVoicesList unloads any loaded voice
|
|
123
|
+
if (currentVoice != null) {
|
|
124
|
+
tts!!.load(currentVoice, "MODE=prep_full")
|
|
125
|
+
logInfo("Reloaded voice after listing: $currentVoice")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
voices
|
|
129
|
+
} catch (e: Exception) {
|
|
130
|
+
logError("Failed to get voices", e)
|
|
131
|
+
emptyList<Map<String, Any>>()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
AsyncFunction("loadVoiceAsync") { voiceName: String ->
|
|
136
|
+
if (tts == null) {
|
|
137
|
+
logWarning("loadVoiceAsync: TTS not initialized")
|
|
138
|
+
return@AsyncFunction false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
val result = tts!!.load(voiceName, "MODE=prep_full")
|
|
143
|
+
if (result == 0) {
|
|
144
|
+
currentVoice = voiceName
|
|
145
|
+
logInfo("Voice loaded: $voiceName")
|
|
146
|
+
true
|
|
147
|
+
} else {
|
|
148
|
+
logWarning("Failed to load voice: $voiceName, error: $result")
|
|
149
|
+
false
|
|
150
|
+
}
|
|
151
|
+
} catch (e: Exception) {
|
|
152
|
+
logError("Failed to load voice: $voiceName", e)
|
|
153
|
+
false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
AsyncFunction("speakAsync") { text: String ->
|
|
158
|
+
if (tts == null) {
|
|
159
|
+
logWarning("speakAsync: TTS not initialized")
|
|
160
|
+
return@AsyncFunction -1
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
if (tts!!.isSpeaking) {
|
|
165
|
+
tts!!.stop()
|
|
166
|
+
}
|
|
167
|
+
val result = tts!!.speak(text)
|
|
168
|
+
if (result < 0) {
|
|
169
|
+
logWarning("speak returned error: $result")
|
|
170
|
+
}
|
|
171
|
+
result
|
|
172
|
+
} catch (e: Exception) {
|
|
173
|
+
logError("Failed to speak", e)
|
|
174
|
+
-1
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Function("stop") {
|
|
179
|
+
try {
|
|
180
|
+
tts?.stop()
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
logError("Failed to stop", e)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
Function("isSpeaking") {
|
|
187
|
+
try {
|
|
188
|
+
tts?.isSpeaking ?: false
|
|
189
|
+
} catch (e: Exception) {
|
|
190
|
+
false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Function("setSpeechRate") { rate: Float ->
|
|
195
|
+
try {
|
|
196
|
+
// JS sends 0.5-2.0, Acapela expects 30-400 (percentage)
|
|
197
|
+
val acapelaRate = rate * 100f
|
|
198
|
+
tts?.setSpeechRate(acapelaRate)
|
|
199
|
+
} catch (e: Exception) {
|
|
200
|
+
logError("Failed to set speech rate", e)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
Function("setPitch") { pitch: Float ->
|
|
205
|
+
try {
|
|
206
|
+
tts?.setPitch(pitch)
|
|
207
|
+
} catch (e: Exception) {
|
|
208
|
+
logError("Failed to set pitch", e)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Function("setAudioBoost") { boost: Int ->
|
|
213
|
+
try {
|
|
214
|
+
tts?.setAudioBoost(boost)
|
|
215
|
+
} catch (e: Exception) {
|
|
216
|
+
logError("Failed to set audio boost", e)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
AsyncFunction("shutdownAsync") {
|
|
221
|
+
try {
|
|
222
|
+
tts?.stop()
|
|
223
|
+
tts?.shutdown()
|
|
224
|
+
tts = null
|
|
225
|
+
isInitialized = false
|
|
226
|
+
currentVoice = null
|
|
227
|
+
logInfo("Shutdown complete")
|
|
228
|
+
} catch (e: Exception) {
|
|
229
|
+
logError("Failed to shutdown", e)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// iTTSEventsCallback implementation
|
|
235
|
+
override fun ttsevents(type: Long, param1: Long, param2: Long, param3: Long, param4: Long) {
|
|
236
|
+
try {
|
|
237
|
+
when (type) {
|
|
238
|
+
acattsandroid.EVENT_AUDIO_START.toLong() -> {
|
|
239
|
+
logInfo("Speech started (text index: $param1)")
|
|
240
|
+
sendEvent("onSpeechStart", emptyMap<String, Any>())
|
|
241
|
+
}
|
|
242
|
+
acattsandroid.EVENT_AUDIO_END.toLong() -> {
|
|
243
|
+
logInfo("Speech ended (text index: $param1, samples: $param2)")
|
|
244
|
+
sendEvent("onSpeechEnd", emptyMap<String, Any>())
|
|
245
|
+
}
|
|
246
|
+
acattsandroid.EVENT_TEXT_START.toLong() -> {
|
|
247
|
+
logInfo("Text processing started (index: $param1)")
|
|
248
|
+
}
|
|
249
|
+
acattsandroid.EVENT_TEXT_END.toLong() -> {
|
|
250
|
+
logInfo("Text processing ended (index: $param1)")
|
|
251
|
+
}
|
|
252
|
+
acattsandroid.EVENT_WORD_POS.toLong() -> {
|
|
253
|
+
// Word position event - available for future text highlighting
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch (e: Exception) {
|
|
257
|
+
logError("Error in ttsevents callback", e)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private fun extractSpeakerName(voiceFileName: String): String {
|
|
262
|
+
// Voice file names like "non_kari_22k_ns.qvcu" -> "Kari"
|
|
263
|
+
// Or voice names like "hq-ref-Norwegian-Kari-22khz" -> "Kari"
|
|
264
|
+
val parts = voiceFileName.split("-", "_")
|
|
265
|
+
return parts.find { it.length > 2 && it[0].isUpperCase() && it.all { c -> c.isLetter() } }
|
|
266
|
+
?: voiceFileName
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private fun logInfo(msg: String) = Log.i(TAG, msg)
|
|
270
|
+
private fun logWarning(msg: String) = Log.w(TAG, msg)
|
|
271
|
+
private fun logError(msg: String, e: Throwable) = Log.e(TAG, msg, e)
|
|
272
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type AcapelaVoice = {
|
|
2
|
+
name: string;
|
|
3
|
+
locale: string;
|
|
4
|
+
language: string;
|
|
5
|
+
speaker: string;
|
|
6
|
+
gender: 'male' | 'female';
|
|
7
|
+
quality: string;
|
|
8
|
+
age: string;
|
|
9
|
+
frequency: string;
|
|
10
|
+
};
|
|
11
|
+
export type ExpoAcapelaTtsModuleEvents = {
|
|
12
|
+
onSpeechStart: () => void;
|
|
13
|
+
onSpeechEnd: () => void;
|
|
14
|
+
onError: (event: {
|
|
15
|
+
message: string;
|
|
16
|
+
code: number;
|
|
17
|
+
}) => void;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=ExpoAcapelaTts.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTts.types.d.ts","sourceRoot":"","sources":["../src/ExpoAcapelaTts.types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTts.types.js","sourceRoot":"","sources":["../src/ExpoAcapelaTts.types.ts"],"names":[],"mappings":"","sourcesContent":["export type AcapelaVoice = {\n name: string;\n locale: string;\n language: string;\n speaker: string;\n gender: 'male' | 'female';\n quality: string;\n age: string;\n frequency: string;\n};\n\nexport type ExpoAcapelaTtsModuleEvents = {\n onSpeechStart: () => void;\n onSpeechEnd: () => void;\n onError: (event: { message: string; code: number }) => void;\n};\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NativeModule } from 'expo';
|
|
2
|
+
import { AcapelaVoice, ExpoAcapelaTtsModuleEvents } from './ExpoAcapelaTts.types';
|
|
3
|
+
declare class ExpoAcapelaTtsModule extends NativeModule<ExpoAcapelaTtsModuleEvents> {
|
|
4
|
+
isAvailable(): boolean;
|
|
5
|
+
initializeAsync(): Promise<boolean>;
|
|
6
|
+
getVoicesAsync(): Promise<AcapelaVoice[]>;
|
|
7
|
+
loadVoiceAsync(voiceName: string): Promise<boolean>;
|
|
8
|
+
speakAsync(text: string): Promise<number>;
|
|
9
|
+
stop(): void;
|
|
10
|
+
isSpeaking(): boolean;
|
|
11
|
+
setSpeechRate(rate: number): void;
|
|
12
|
+
setPitch(pitch: number): void;
|
|
13
|
+
setAudioBoost(boost: number): void;
|
|
14
|
+
shutdownAsync(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
declare const _default: ExpoAcapelaTtsModule;
|
|
17
|
+
export default _default;
|
|
18
|
+
//# sourceMappingURL=ExpoAcapelaTtsModule.android.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTtsModule.android.d.ts","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC3B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,OAAO,oBAAqB,SAAQ,YAAY,CAAC,0BAA0B,CAAC;IACjF,WAAW,IAAI,OAAO;IACtB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IACnC,cAAc,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IACzC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACnD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACzC,IAAI,IAAI,IAAI;IACZ,UAAU,IAAI,OAAO;IACrB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAC7B,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAClC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B;;AAED,wBAA2E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTtsModule.android.js","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAqBzD,eAAe,mBAAmB,CAAuB,gBAAgB,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport {\n AcapelaVoice,\n ExpoAcapelaTtsModuleEvents,\n} from './ExpoAcapelaTts.types';\n\ndeclare class ExpoAcapelaTtsModule extends NativeModule<ExpoAcapelaTtsModuleEvents> {\n isAvailable(): boolean;\n initializeAsync(): Promise<boolean>;\n getVoicesAsync(): Promise<AcapelaVoice[]>;\n loadVoiceAsync(voiceName: string): Promise<boolean>;\n speakAsync(text: string): Promise<number>;\n stop(): void;\n isSpeaking(): boolean;\n setSpeechRate(rate: number): void;\n setPitch(pitch: number): void;\n setAudioBoost(boost: number): void;\n shutdownAsync(): Promise<void>;\n}\n\nexport default requireNativeModule<ExpoAcapelaTtsModule>('ExpoAcapelaTts');\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
isAvailable(): boolean;
|
|
3
|
+
initializeAsync(): Promise<boolean>;
|
|
4
|
+
getVoicesAsync(): Promise<never[]>;
|
|
5
|
+
loadVoiceAsync(_voiceName: string): Promise<boolean>;
|
|
6
|
+
speakAsync(_text: string): Promise<number>;
|
|
7
|
+
stop(): void;
|
|
8
|
+
isSpeaking(): boolean;
|
|
9
|
+
setSpeechRate(_rate: number): void;
|
|
10
|
+
setPitch(_pitch: number): void;
|
|
11
|
+
setAudioBoost(_boost: number): void;
|
|
12
|
+
shutdownAsync(): Promise<void>;
|
|
13
|
+
addListener(): {
|
|
14
|
+
remove(): void;
|
|
15
|
+
};
|
|
16
|
+
removeListeners(): void;
|
|
17
|
+
};
|
|
18
|
+
export default _default;
|
|
19
|
+
//# sourceMappingURL=ExpoAcapelaTtsModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTtsModule.d.ts","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.ts"],"names":[],"mappings":";mBAEiB,OAAO;uBAGG,OAAO,CAAC,OAAO,CAAC;;+BAMR,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;sBAGlC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;;kBAIlC,OAAO;yBAGA,MAAM;qBACV,MAAM;0BACD,MAAM;;;;;;;AAtB9B,wBA4BE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// No-op stub for non-Android platforms
|
|
2
|
+
export default {
|
|
3
|
+
isAvailable() {
|
|
4
|
+
return false;
|
|
5
|
+
},
|
|
6
|
+
async initializeAsync() {
|
|
7
|
+
return false;
|
|
8
|
+
},
|
|
9
|
+
async getVoicesAsync() {
|
|
10
|
+
return [];
|
|
11
|
+
},
|
|
12
|
+
async loadVoiceAsync(_voiceName) {
|
|
13
|
+
return false;
|
|
14
|
+
},
|
|
15
|
+
async speakAsync(_text) {
|
|
16
|
+
return -1;
|
|
17
|
+
},
|
|
18
|
+
stop() { },
|
|
19
|
+
isSpeaking() {
|
|
20
|
+
return false;
|
|
21
|
+
},
|
|
22
|
+
setSpeechRate(_rate) { },
|
|
23
|
+
setPitch(_pitch) { },
|
|
24
|
+
setAudioBoost(_boost) { },
|
|
25
|
+
async shutdownAsync() { },
|
|
26
|
+
addListener() {
|
|
27
|
+
return { remove() { } };
|
|
28
|
+
},
|
|
29
|
+
removeListeners() { },
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=ExpoAcapelaTtsModule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTtsModule.js","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,eAAe;IACb,WAAW;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,eAAe;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,cAAc;QAClB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,KAAK,CAAC,cAAc,CAAC,UAAkB;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,KAAa;QAC5B,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,IAAI,KAAI,CAAC;IACT,UAAU;QACR,OAAO,KAAK,CAAC;IACf,CAAC;IACD,aAAa,CAAC,KAAa,IAAG,CAAC;IAC/B,QAAQ,CAAC,MAAc,IAAG,CAAC;IAC3B,aAAa,CAAC,MAAc,IAAG,CAAC;IAChC,KAAK,CAAC,aAAa,KAAI,CAAC;IACxB,WAAW;QACT,OAAO,EAAE,MAAM,KAAI,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,eAAe,KAAI,CAAC;CACrB,CAAC","sourcesContent":["// No-op stub for non-Android platforms\nexport default {\n isAvailable(): boolean {\n return false;\n },\n async initializeAsync(): Promise<boolean> {\n return false;\n },\n async getVoicesAsync() {\n return [];\n },\n async loadVoiceAsync(_voiceName: string): Promise<boolean> {\n return false;\n },\n async speakAsync(_text: string): Promise<number> {\n return -1;\n },\n stop() {},\n isSpeaking(): boolean {\n return false;\n },\n setSpeechRate(_rate: number) {},\n setPitch(_pitch: number) {},\n setAudioBoost(_boost: number) {},\n async shutdownAsync() {},\n addListener() {\n return { remove() {} };\n },\n removeListeners() {},\n};\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,cAAc,wBAAwB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,cAAc,wBAAwB,CAAC","sourcesContent":["export { default } from './ExpoAcapelaTtsModule';\nexport * from './ExpoAcapelaTts.types';\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@focus8/expo-acapela-tts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Acapela TTS integration for Expo on Android",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "expo-module build",
|
|
9
|
+
"clean": "expo-module clean",
|
|
10
|
+
"lint": "expo-module lint",
|
|
11
|
+
"test": "expo-module test",
|
|
12
|
+
"prepare": "expo-module prepare",
|
|
13
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
14
|
+
"expo-module": "expo-module"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"react-native",
|
|
21
|
+
"expo",
|
|
22
|
+
"acapela",
|
|
23
|
+
"tts",
|
|
24
|
+
"text-to-speech"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/focus8-no/expo-acapela-tts.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/focus8-no/expo-acapela-tts/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/focus8-no/expo-acapela-tts#readme",
|
|
34
|
+
"author": "Kjartan <kjartan@focus8.no>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "~19.1.0",
|
|
39
|
+
"expo-module-scripts": "^5.0.7",
|
|
40
|
+
"expo": "^54.0.12",
|
|
41
|
+
"react-native": "0.81.4"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"expo": "*",
|
|
45
|
+
"react": "*",
|
|
46
|
+
"react-native": "*"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type AcapelaVoice = {
|
|
2
|
+
name: string;
|
|
3
|
+
locale: string;
|
|
4
|
+
language: string;
|
|
5
|
+
speaker: string;
|
|
6
|
+
gender: 'male' | 'female';
|
|
7
|
+
quality: string;
|
|
8
|
+
age: string;
|
|
9
|
+
frequency: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ExpoAcapelaTtsModuleEvents = {
|
|
13
|
+
onSpeechStart: () => void;
|
|
14
|
+
onSpeechEnd: () => void;
|
|
15
|
+
onError: (event: { message: string; code: number }) => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NativeModule, requireNativeModule } from 'expo';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AcapelaVoice,
|
|
5
|
+
ExpoAcapelaTtsModuleEvents,
|
|
6
|
+
} from './ExpoAcapelaTts.types';
|
|
7
|
+
|
|
8
|
+
declare class ExpoAcapelaTtsModule extends NativeModule<ExpoAcapelaTtsModuleEvents> {
|
|
9
|
+
isAvailable(): boolean;
|
|
10
|
+
initializeAsync(): Promise<boolean>;
|
|
11
|
+
getVoicesAsync(): Promise<AcapelaVoice[]>;
|
|
12
|
+
loadVoiceAsync(voiceName: string): Promise<boolean>;
|
|
13
|
+
speakAsync(text: string): Promise<number>;
|
|
14
|
+
stop(): void;
|
|
15
|
+
isSpeaking(): boolean;
|
|
16
|
+
setSpeechRate(rate: number): void;
|
|
17
|
+
setPitch(pitch: number): void;
|
|
18
|
+
setAudioBoost(boost: number): void;
|
|
19
|
+
shutdownAsync(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default requireNativeModule<ExpoAcapelaTtsModule>('ExpoAcapelaTts');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// No-op stub for non-Android platforms
|
|
2
|
+
export default {
|
|
3
|
+
isAvailable(): boolean {
|
|
4
|
+
return false;
|
|
5
|
+
},
|
|
6
|
+
async initializeAsync(): Promise<boolean> {
|
|
7
|
+
return false;
|
|
8
|
+
},
|
|
9
|
+
async getVoicesAsync() {
|
|
10
|
+
return [];
|
|
11
|
+
},
|
|
12
|
+
async loadVoiceAsync(_voiceName: string): Promise<boolean> {
|
|
13
|
+
return false;
|
|
14
|
+
},
|
|
15
|
+
async speakAsync(_text: string): Promise<number> {
|
|
16
|
+
return -1;
|
|
17
|
+
},
|
|
18
|
+
stop() {},
|
|
19
|
+
isSpeaking(): boolean {
|
|
20
|
+
return false;
|
|
21
|
+
},
|
|
22
|
+
setSpeechRate(_rate: number) {},
|
|
23
|
+
setPitch(_pitch: number) {},
|
|
24
|
+
setAudioBoost(_boost: number) {},
|
|
25
|
+
async shutdownAsync() {},
|
|
26
|
+
addListener() {
|
|
27
|
+
return { remove() {} };
|
|
28
|
+
},
|
|
29
|
+
removeListeners() {},
|
|
30
|
+
};
|
package/src/index.ts
ADDED