@focus8/expo-acapela-tts 0.1.4 → 0.1.6
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/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/java/expo/modules/acapelatts/ExpoAcapelaTtsModule.kt +59 -0
- package/build/ExpoAcapelaTtsModule.android.d.ts +2 -0
- package/build/ExpoAcapelaTtsModule.android.d.ts.map +1 -1
- package/build/ExpoAcapelaTtsModule.android.js.map +1 -1
- package/build/ExpoAcapelaTtsModule.d.ts +2 -0
- package/build/ExpoAcapelaTtsModule.d.ts.map +1 -1
- package/build/ExpoAcapelaTtsModule.js +6 -0
- package/build/ExpoAcapelaTtsModule.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoAcapelaTtsModule.android.ts +2 -0
- package/src/ExpoAcapelaTtsModule.ts +6 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
3
|
+
|
|
4
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
5
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
6
|
+
tools:ignore="ScopedStorage" />
|
|
7
|
+
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
|
8
|
+
tools:ignore="ScopedStorage" />
|
|
9
|
+
|
|
10
|
+
</manifest>
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
package expo.modules.acapelatts
|
|
2
2
|
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.Environment
|
|
9
|
+
import android.provider.Settings
|
|
3
10
|
import android.util.Log
|
|
11
|
+
import androidx.core.content.ContextCompat
|
|
4
12
|
import com.acapelagroup.android.tts.acattsandroid
|
|
5
13
|
import com.acapelagroup.android.tts.acattsandroid.iTTSEventsCallback
|
|
6
14
|
import expo.modules.kotlin.modules.Module
|
|
@@ -39,6 +47,48 @@ class ExpoAcapelaTtsModule : Module(), iTTSEventsCallback {
|
|
|
39
47
|
|
|
40
48
|
Events("onSpeechStart", "onSpeechEnd", "onError")
|
|
41
49
|
|
|
50
|
+
Function("hasStoragePermission") {
|
|
51
|
+
hasStorageAccess()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
AsyncFunction("requestStoragePermissionAsync") {
|
|
55
|
+
val context = appContext.reactContext ?: return@AsyncFunction false
|
|
56
|
+
|
|
57
|
+
if (hasStorageAccess()) {
|
|
58
|
+
logInfo("Storage permission already granted")
|
|
59
|
+
return@AsyncFunction true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
63
|
+
// Android 11+ needs MANAGE_EXTERNAL_STORAGE via Settings intent
|
|
64
|
+
try {
|
|
65
|
+
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
|
66
|
+
data = Uri.fromParts("package", context.packageName, null)
|
|
67
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
68
|
+
}
|
|
69
|
+
context.startActivity(intent)
|
|
70
|
+
logInfo("Launched MANAGE_EXTERNAL_STORAGE settings for ${context.packageName}")
|
|
71
|
+
} catch (e: Exception) {
|
|
72
|
+
// Fallback for devices that don't support the per-app intent
|
|
73
|
+
try {
|
|
74
|
+
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION).apply {
|
|
75
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
76
|
+
}
|
|
77
|
+
context.startActivity(intent)
|
|
78
|
+
} catch (e2: Exception) {
|
|
79
|
+
logError("Failed to launch storage permission settings", e2)
|
|
80
|
+
return@AsyncFunction false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// User needs to grant in settings and come back - we can't wait for result
|
|
84
|
+
false
|
|
85
|
+
} else {
|
|
86
|
+
// Pre-Android 11: READ_EXTERNAL_STORAGE is enough, granted via manifest on enrolled AOSP
|
|
87
|
+
logInfo("Pre-Android 11: storage permissions handled by manifest")
|
|
88
|
+
true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
42
92
|
Function("isAvailable") {
|
|
43
93
|
if (!nativeLibLoaded) {
|
|
44
94
|
return@Function false
|
|
@@ -287,6 +337,15 @@ class ExpoAcapelaTtsModule : Module(), iTTSEventsCallback {
|
|
|
287
337
|
}
|
|
288
338
|
}
|
|
289
339
|
|
|
340
|
+
private fun hasStorageAccess(): Boolean {
|
|
341
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
342
|
+
Environment.isExternalStorageManager()
|
|
343
|
+
} else {
|
|
344
|
+
val context = appContext.reactContext ?: return false
|
|
345
|
+
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
290
349
|
private fun extractSpeakerName(voiceFileName: String): String {
|
|
291
350
|
// Voice file names like "non_kari_22k_ns.qvcu" -> "Kari"
|
|
292
351
|
// Or voice names like "hq-ref-Norwegian-Kari-22khz" -> "Kari"
|
|
@@ -2,6 +2,8 @@ import { NativeModule } from 'expo';
|
|
|
2
2
|
import { AcapelaVoice, ExpoAcapelaTtsModuleEvents } from './ExpoAcapelaTts.types';
|
|
3
3
|
declare class ExpoAcapelaTtsModule extends NativeModule<ExpoAcapelaTtsModuleEvents> {
|
|
4
4
|
isAvailable(): boolean;
|
|
5
|
+
hasStoragePermission(): boolean;
|
|
6
|
+
requestStoragePermissionAsync(): Promise<boolean>;
|
|
5
7
|
addVoicePath(path: string): void;
|
|
6
8
|
setLicense(userId: number, password: number, key: string): void;
|
|
7
9
|
initializeAsync(): Promise<boolean>;
|
|
@@ -1 +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,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAChC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAC/D,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"}
|
|
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,oBAAoB,IAAI,OAAO;IAC/B,6BAA6B,IAAI,OAAO,CAAC,OAAO,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAChC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAC/D,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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAcapelaTtsModule.android.js","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTtsModule.android.js","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAyBzD,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 hasStoragePermission(): boolean;\n requestStoragePermissionAsync(): Promise<boolean>;\n addVoicePath(path: string): void;\n setLicense(userId: number, password: number, key: string): void;\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"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
2
|
isAvailable(): boolean;
|
|
3
|
+
hasStoragePermission(): boolean;
|
|
4
|
+
requestStoragePermissionAsync(): Promise<boolean>;
|
|
3
5
|
addVoicePath(_path: string): void;
|
|
4
6
|
setLicense(_userId: number, _password: number, _key: string): void;
|
|
5
7
|
initializeAsync(): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAcapelaTtsModule.d.ts","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.ts"],"names":[],"mappings":";mBAEiB,OAAO;
|
|
1
|
+
{"version":3,"file":"ExpoAcapelaTtsModule.d.ts","sourceRoot":"","sources":["../src/ExpoAcapelaTtsModule.ts"],"names":[],"mappings":";mBAEiB,OAAO;4BAGE,OAAO;qCAGQ,OAAO,CAAC,OAAO,CAAC;wBAGnC,MAAM;wBACN,MAAM,aAAa,MAAM,QAAQ,MAAM;uBAClC,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;;;;;;;AA9B9B,wBAoCE"}
|
|
@@ -3,6 +3,12 @@ export default {
|
|
|
3
3
|
isAvailable() {
|
|
4
4
|
return false;
|
|
5
5
|
},
|
|
6
|
+
hasStoragePermission() {
|
|
7
|
+
return true;
|
|
8
|
+
},
|
|
9
|
+
async requestStoragePermissionAsync() {
|
|
10
|
+
return true;
|
|
11
|
+
},
|
|
6
12
|
addVoicePath(_path) { },
|
|
7
13
|
setLicense(_userId, _password, _key) { },
|
|
8
14
|
async initializeAsync() {
|
|
@@ -1 +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,YAAY,CAAC,KAAa,IAAG,CAAC;IAC9B,UAAU,CAAC,OAAe,EAAE,SAAiB,EAAE,IAAY,IAAG,CAAC;IAC/D,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 addVoicePath(_path: string) {},\n setLicense(_userId: number, _password: number, _key: string) {},\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"]}
|
|
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,oBAAoB;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,KAAK,CAAC,6BAA6B;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,YAAY,CAAC,KAAa,IAAG,CAAC;IAC9B,UAAU,CAAC,OAAe,EAAE,SAAiB,EAAE,IAAY,IAAG,CAAC;IAC/D,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 hasStoragePermission(): boolean {\n return true;\n },\n async requestStoragePermissionAsync(): Promise<boolean> {\n return true;\n },\n addVoicePath(_path: string) {},\n setLicense(_userId: number, _password: number, _key: string) {},\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/package.json
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
|
|
8
8
|
declare class ExpoAcapelaTtsModule extends NativeModule<ExpoAcapelaTtsModuleEvents> {
|
|
9
9
|
isAvailable(): boolean;
|
|
10
|
+
hasStoragePermission(): boolean;
|
|
11
|
+
requestStoragePermissionAsync(): Promise<boolean>;
|
|
10
12
|
addVoicePath(path: string): void;
|
|
11
13
|
setLicense(userId: number, password: number, key: string): void;
|
|
12
14
|
initializeAsync(): Promise<boolean>;
|
|
@@ -3,6 +3,12 @@ export default {
|
|
|
3
3
|
isAvailable(): boolean {
|
|
4
4
|
return false;
|
|
5
5
|
},
|
|
6
|
+
hasStoragePermission(): boolean {
|
|
7
|
+
return true;
|
|
8
|
+
},
|
|
9
|
+
async requestStoragePermissionAsync(): Promise<boolean> {
|
|
10
|
+
return true;
|
|
11
|
+
},
|
|
6
12
|
addVoicePath(_path: string) {},
|
|
7
13
|
setLicense(_userId: number, _password: number, _key: string) {},
|
|
8
14
|
async initializeAsync(): Promise<boolean> {
|