react-native-kookit 0.3.5 → 0.3.7
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 +22 -1
- package/TTS_CHECKLIST.md +256 -0
- package/android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt +284 -0
- package/build/ReactNativeKookit.types.d.ts +11 -0
- package/build/ReactNativeKookit.types.d.ts.map +1 -1
- package/build/ReactNativeKookit.types.js.map +1 -1
- package/build/ReactNativeKookitModule.d.ts +37 -1
- package/build/ReactNativeKookitModule.d.ts.map +1 -1
- package/build/ReactNativeKookitModule.js.map +1 -1
- package/ios/ReactNativeKookitModule.swift +143 -0
- package/package.json +1 -1
- package/ANDROID_BUILD_FIX.md +0 -117
- package/ANDROID_FTP_UPDATE.md +0 -161
- package/ANDROID_SETUP.md +0 -188
- package/ANDROID_SMB_LIBRARY_COMPARISON.md +0 -170
- package/ANDROID_SMB_LISTSHARES_FIX.md +0 -100
- package/API_UNIFICATION.md +0 -180
- package/EXPO_PLUGIN_README.md +0 -136
- package/FTP_CLIENT_API.md +0 -301
- package/FTP_FILE_LIST_ENHANCEMENT.md +0 -186
- package/FTP_FILE_OPERATIONS.md +0 -0
- package/FTP_README.md +0 -322
- package/README_NEW.md +0 -143
- package/RELEASE_CHECKLIST.md +0 -115
- package/SMB_CLIENT_STATIC_METHODS.md +0 -175
- package/SMB_COMPLETE_GUIDE.md +0 -308
- package/SMB_HYBRID_IMPLEMENTATION.md +0 -160
- package/SMB_IMPLEMENTATION_SUMMARY.md +0 -229
- package/SMB_USAGE.md +0 -275
- package/TROUBLESHOOTING.md +0 -132
- /package/{ANDROID_PARAMETER_FIX.md → CHANGELOG_CONTENT_URI.md} +0 -0
- /package/{FTP_EXAMPLE_IMPLEMENTATION.md → IMPLEMENTATION_SUMMARY.md} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
# react-native-kookit
|
|
2
2
|
|
|
3
|
-
A React Native module
|
|
3
|
+
A comprehensive React Native/Expo module with multiple utilities for Android and iOS.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎵 **Volume Key Interception** - Capture volume button presses on both iOS and Android
|
|
8
|
+
- 📁 **FTP Client** - Full-featured FTP client with upload/download capabilities
|
|
9
|
+
- 🗂️ **SMB Client** - Access SMB/CIFS network shares
|
|
10
|
+
- 📱 **Content URI Handler** (Android) - Handle `content://` URIs from other apps, bypassing permission restrictions
|
|
11
|
+
|
|
12
|
+
### Content URI Handler
|
|
13
|
+
|
|
14
|
+
The Content URI Handler solves a common Android problem: when receiving files via "Open with" or "Share" intents, apps receive `content://` URIs that often trigger `SecurityException: Permission Denial` errors.
|
|
15
|
+
|
|
16
|
+
**Key Benefits:**
|
|
17
|
+
|
|
18
|
+
- ✅ Bypass permission restrictions using Android's ContentResolver
|
|
19
|
+
- ✅ No `READ_EXTERNAL_STORAGE` permission needed
|
|
20
|
+
- ✅ Works with all third-party apps and file managers
|
|
21
|
+
- ✅ Get file metadata (name, size, MIME type)
|
|
22
|
+
- ✅ Copy to app cache directory for further processing
|
|
23
|
+
|
|
24
|
+
📖 [**View Content URI Handler Documentation**](./docs/CONTENT_URI_HANDLER.md)
|
|
4
25
|
|
|
5
26
|
# API documentation
|
|
6
27
|
|
package/TTS_CHECKLIST.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# TTS 功能实现检查清单
|
|
2
|
+
|
|
3
|
+
## ✅ 已完成的任务
|
|
4
|
+
|
|
5
|
+
### 1. TypeScript 类型定义
|
|
6
|
+
|
|
7
|
+
- [x] 在 `src/ReactNativeKookit.types.ts` 中添加 `TtsSynthesizeOptions` 类型
|
|
8
|
+
- [x] 在 `src/ReactNativeKookit.types.ts` 中添加 `TtsSynthesizeResult` 类型
|
|
9
|
+
- [x] 类型包含所有必需和可选参数
|
|
10
|
+
- [x] 添加了清晰的注释说明
|
|
11
|
+
|
|
12
|
+
### 2. TypeScript 模块接口
|
|
13
|
+
|
|
14
|
+
- [x] 在 `src/ReactNativeKookitModule.ts` 中导入 TTS 类型
|
|
15
|
+
- [x] 声明 `synthesizeSpeech` 方法
|
|
16
|
+
- [x] 添加 JSDoc 注释
|
|
17
|
+
- [x] 正确的类型签名和返回值
|
|
18
|
+
|
|
19
|
+
### 3. Android 原生实现
|
|
20
|
+
|
|
21
|
+
- [x] 导入必要的 Android TTS 相关类
|
|
22
|
+
- [x] 添加 TTS 实例变量
|
|
23
|
+
- [x] 实现 `synthesizeSpeech` AsyncFunction
|
|
24
|
+
- [x] TTS 初始化逻辑(异步等待)
|
|
25
|
+
- [x] 语言设置和验证
|
|
26
|
+
- [x] 语音ID支持(可选)
|
|
27
|
+
- [x] 音调和语速设置
|
|
28
|
+
- [x] 创建缓存目录 `tts`
|
|
29
|
+
- [x] 生成唯一文件名(UUID)
|
|
30
|
+
- [x] 合成音频到 WAV 文件
|
|
31
|
+
- [x] 实现 `parseLocale()` 辅助函数
|
|
32
|
+
- [x] 实现 `estimateDuration()` 辅助函数
|
|
33
|
+
- [x] 在 `onDestroy()` 中清理 TTS 资源
|
|
34
|
+
- [x] 完善的错误处理
|
|
35
|
+
|
|
36
|
+
### 4. iOS 原生实现
|
|
37
|
+
|
|
38
|
+
- [x] 添加 `AVSpeechSynthesizer` 实例变量
|
|
39
|
+
- [x] 实现 `synthesizeSpeech` AsyncFunction
|
|
40
|
+
- [x] 初始化语音合成器
|
|
41
|
+
- [x] 语言设置和语音选择
|
|
42
|
+
- [x] 支持语音ID(可选)
|
|
43
|
+
- [x] 音调和语速设置
|
|
44
|
+
- [x] 创建缓存目录 `tts`
|
|
45
|
+
- [x] 生成唯一文件名(UUID)
|
|
46
|
+
- [x] 使用 `AVSpeechSynthesizer.write()` 生成音频
|
|
47
|
+
- [x] 实现 `synthesizeSpeechToFile()` 核心方法
|
|
48
|
+
- [x] 实现 `writeBuffersToFile()` 音频写入方法
|
|
49
|
+
- [x] 实现 `estimateDuration()` 辅助函数
|
|
50
|
+
- [x] 使用 async/await 处理异步操作
|
|
51
|
+
- [x] 完善的错误处理
|
|
52
|
+
|
|
53
|
+
### 5. 导出和集成
|
|
54
|
+
|
|
55
|
+
- [x] `src/index.ts` 已通过 `export *` 自动导出 TTS 类型
|
|
56
|
+
- [x] 模块可以直接使用
|
|
57
|
+
|
|
58
|
+
### 6. 示例代码
|
|
59
|
+
|
|
60
|
+
- [x] 创建 `example/TtsExample.tsx` 完整示例
|
|
61
|
+
- [x] 包含文本输入界面
|
|
62
|
+
- [x] 语言选择功能
|
|
63
|
+
- [x] 音调和语速调整
|
|
64
|
+
- [x] 错误处理展示
|
|
65
|
+
- [x] 结果显示
|
|
66
|
+
- [x] 友好的用户界面
|
|
67
|
+
|
|
68
|
+
### 7. 文档
|
|
69
|
+
|
|
70
|
+
- [x] 创建 `docs/TTS_FEATURE.md` 完整文档
|
|
71
|
+
- API 说明
|
|
72
|
+
- 使用示例
|
|
73
|
+
- 平台详情
|
|
74
|
+
- 支持的语言
|
|
75
|
+
- 参数指南
|
|
76
|
+
- 错误处理
|
|
77
|
+
- 最佳实践
|
|
78
|
+
- 故障排除
|
|
79
|
+
|
|
80
|
+
- [x] 创建 `docs/TTS_QUICK_START.md` 快速入门
|
|
81
|
+
- 快速示例
|
|
82
|
+
- 常用语言代码
|
|
83
|
+
- 文件位置说明
|
|
84
|
+
|
|
85
|
+
- [x] 创建 `docs/TTS_IMPLEMENTATION_SUMMARY.md` 实现总结
|
|
86
|
+
- 技术细节
|
|
87
|
+
- 文件清单
|
|
88
|
+
- 后续改进建议
|
|
89
|
+
|
|
90
|
+
- [x] 创建 `docs/TTS_README_SECTION.md` README片段
|
|
91
|
+
- 可直接添加到主README
|
|
92
|
+
|
|
93
|
+
### 8. 测试和验证
|
|
94
|
+
|
|
95
|
+
- [x] TypeScript 编译通过
|
|
96
|
+
- [x] 无编译错误
|
|
97
|
+
- [x] 类型检查通过
|
|
98
|
+
- [x] 代码构建成功 (`npm run build`)
|
|
99
|
+
|
|
100
|
+
## 📋 功能特性验证
|
|
101
|
+
|
|
102
|
+
### 核心功能
|
|
103
|
+
|
|
104
|
+
- [x] 文本转语音合成
|
|
105
|
+
- [x] 支持自定义语言
|
|
106
|
+
- [x] 支持音调调整(0.5-2.0)
|
|
107
|
+
- [x] 支持语速调整(0.5-2.0)
|
|
108
|
+
- [x] 可选语音ID
|
|
109
|
+
- [x] 返回文件路径
|
|
110
|
+
- [x] 返回预估时长
|
|
111
|
+
|
|
112
|
+
### 平台支持
|
|
113
|
+
|
|
114
|
+
- [x] Android 实现(TextToSpeech API)
|
|
115
|
+
- [x] iOS 实现(AVSpeechSynthesizer)
|
|
116
|
+
- [x] 跨平台统一API
|
|
117
|
+
|
|
118
|
+
### 文件管理
|
|
119
|
+
|
|
120
|
+
- [x] 自动创建缓存目录
|
|
121
|
+
- [x] 唯一文件命名(UUID)
|
|
122
|
+
- [x] Android: WAV 格式
|
|
123
|
+
- [x] iOS: CAF 格式
|
|
124
|
+
|
|
125
|
+
### 开发体验
|
|
126
|
+
|
|
127
|
+
- [x] TypeScript 完整类型支持
|
|
128
|
+
- [x] Promise-based API
|
|
129
|
+
- [x] 清晰的错误消息
|
|
130
|
+
- [x] 详细的文档
|
|
131
|
+
- [x] 实用的示例
|
|
132
|
+
|
|
133
|
+
## 📁 文件清单
|
|
134
|
+
|
|
135
|
+
### 修改的文件(5个)
|
|
136
|
+
|
|
137
|
+
1. ✅ `src/ReactNativeKookit.types.ts` - TTS类型定义
|
|
138
|
+
2. ✅ `src/ReactNativeKookitModule.ts` - TTS方法声明
|
|
139
|
+
3. ✅ `android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt` - Android实现
|
|
140
|
+
4. ✅ `ios/ReactNativeKookitModule.swift` - iOS实现
|
|
141
|
+
5. ✅ `src/index.ts` - 导出(已有 export \*)
|
|
142
|
+
|
|
143
|
+
### 新增的文件(5个)
|
|
144
|
+
|
|
145
|
+
1. ✅ `example/TtsExample.tsx` - 完整示例应用
|
|
146
|
+
2. ✅ `docs/TTS_FEATURE.md` - 完整文档
|
|
147
|
+
3. ✅ `docs/TTS_QUICK_START.md` - 快速入门
|
|
148
|
+
4. ✅ `docs/TTS_IMPLEMENTATION_SUMMARY.md` - 实现总结
|
|
149
|
+
5. ✅ `docs/TTS_README_SECTION.md` - README片段
|
|
150
|
+
|
|
151
|
+
## 🎯 API 使用示例
|
|
152
|
+
|
|
153
|
+
### 基础使用
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const result = await ReactNativeKookitModule.synthesizeSpeech({
|
|
157
|
+
text: "Hello World",
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 完整参数
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const result = await ReactNativeKookitModule.synthesizeSpeech({
|
|
165
|
+
text: "你好世界",
|
|
166
|
+
language: "zh-CN",
|
|
167
|
+
voiceId: "optional-voice-id",
|
|
168
|
+
pitch: 1.2,
|
|
169
|
+
rate: 0.9,
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 返回值
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
{
|
|
177
|
+
filePath: "/path/to/cache/tts/tts_uuid.wav", // Android
|
|
178
|
+
// or
|
|
179
|
+
filePath: "/path/to/cache/tts/tts_uuid.caf", // iOS
|
|
180
|
+
duration: 2.5 // seconds
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## ✨ 代码质量
|
|
185
|
+
|
|
186
|
+
- [x] 遵循项目代码风格
|
|
187
|
+
- [x] 使用 TypeScript 严格类型
|
|
188
|
+
- [x] 完善的错误处理
|
|
189
|
+
- [x] 清晰的变量命名
|
|
190
|
+
- [x] 详细的代码注释
|
|
191
|
+
- [x] 模块化设计
|
|
192
|
+
- [x] 资源清理(Android onDestroy)
|
|
193
|
+
|
|
194
|
+
## 🔍 测试状态
|
|
195
|
+
|
|
196
|
+
- [x] TypeScript 编译成功
|
|
197
|
+
- [x] 无语法错误
|
|
198
|
+
- [x] 无类型错误
|
|
199
|
+
- [x] Build 构建成功
|
|
200
|
+
- ⏳ 运行时测试(需要在设备上测试)
|
|
201
|
+
|
|
202
|
+
## 📦 输出文件
|
|
203
|
+
|
|
204
|
+
### Android
|
|
205
|
+
|
|
206
|
+
- **格式**: WAV (PCM)
|
|
207
|
+
- **位置**: `{cacheDir}/tts/`
|
|
208
|
+
- **命名**: `tts_{uuid}.wav`
|
|
209
|
+
- **API**: `TextToSpeech.synthesizeToFile()`
|
|
210
|
+
|
|
211
|
+
### iOS
|
|
212
|
+
|
|
213
|
+
- **格式**: CAF (Core Audio Format)
|
|
214
|
+
- **位置**: `{cachesDirectory}/tts/`
|
|
215
|
+
- **命名**: `tts_{uuid}.caf`
|
|
216
|
+
- **API**: `AVSpeechSynthesizer.write()`
|
|
217
|
+
|
|
218
|
+
## 🌍 语言支持
|
|
219
|
+
|
|
220
|
+
已文档化的常用语言:
|
|
221
|
+
|
|
222
|
+
- 英语(en-US, en-GB, en-AU)
|
|
223
|
+
- 中文(zh-CN, zh-TW, zh-HK)
|
|
224
|
+
- 西班牙语(es-ES, es-MX)
|
|
225
|
+
- 法语(fr-FR)
|
|
226
|
+
- 德语(de-DE)
|
|
227
|
+
- 日语(ja-JP)
|
|
228
|
+
- 韩语(ko-KR)
|
|
229
|
+
- 意大利语(it-IT)
|
|
230
|
+
- 葡萄牙语(pt-BR)
|
|
231
|
+
- 俄语(ru-RU)
|
|
232
|
+
- 阿拉伯语(ar-SA)
|
|
233
|
+
|
|
234
|
+
## 🚀 下一步
|
|
235
|
+
|
|
236
|
+
建议的后续步骤:
|
|
237
|
+
|
|
238
|
+
1. 在真实设备上测试 Android 版本
|
|
239
|
+
2. 在真实设备上测试 iOS 版本
|
|
240
|
+
3. 测试多种语言
|
|
241
|
+
4. 验证音频文件质量
|
|
242
|
+
5. 测试边界情况(长文本、特殊字符等)
|
|
243
|
+
6. 考虑添加单元测试
|
|
244
|
+
|
|
245
|
+
## ✅ 总结
|
|
246
|
+
|
|
247
|
+
TTS 功能已完全实现,包括:
|
|
248
|
+
|
|
249
|
+
- ✅ TypeScript 类型和接口定义
|
|
250
|
+
- ✅ Android 原生实现
|
|
251
|
+
- ✅ iOS 原生实现
|
|
252
|
+
- ✅ 完整的示例代码
|
|
253
|
+
- ✅ 详细的文档
|
|
254
|
+
- ✅ 编译通过,无错误
|
|
255
|
+
|
|
256
|
+
**状态**: 🎉 实现完成,可以使用!
|
|
@@ -11,6 +11,17 @@ import expo.modules.kotlin.exception.Exceptions
|
|
|
11
11
|
import expo.modules.kotlin.Promise
|
|
12
12
|
import kotlinx.coroutines.*
|
|
13
13
|
import java.net.URL
|
|
14
|
+
import android.net.Uri
|
|
15
|
+
import android.provider.OpenableColumns
|
|
16
|
+
import android.database.Cursor
|
|
17
|
+
import java.io.File
|
|
18
|
+
import java.io.FileOutputStream
|
|
19
|
+
import java.io.InputStream
|
|
20
|
+
import android.speech.tts.TextToSpeech
|
|
21
|
+
import android.speech.tts.UtteranceProgressListener
|
|
22
|
+
import android.os.Bundle
|
|
23
|
+
import java.util.Locale
|
|
24
|
+
import java.util.UUID
|
|
14
25
|
|
|
15
26
|
class ReactNativeKookitModule : Module() {
|
|
16
27
|
private var isVolumeListenerEnabled = false
|
|
@@ -20,6 +31,8 @@ class ReactNativeKookitModule : Module() {
|
|
|
20
31
|
private val ftpClients = mutableMapOf<String, FtpClient>()
|
|
21
32
|
private val smbClients = mutableMapOf<String, SmbClient>()
|
|
22
33
|
private val moduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
34
|
+
private var textToSpeech: TextToSpeech? = null
|
|
35
|
+
private var ttsInitialized = false
|
|
23
36
|
|
|
24
37
|
// Each module class must implement the definition function. The definition consists of components
|
|
25
38
|
// that describes the module's functionality and behavior.
|
|
@@ -723,6 +736,204 @@ class ReactNativeKookitModule : Module() {
|
|
|
723
736
|
}
|
|
724
737
|
}
|
|
725
738
|
|
|
739
|
+
// ContentResolver functions for handling content:// URIs
|
|
740
|
+
AsyncFunction("copyContentUriToCache") { contentUri: String, fileName: String?, promise: Promise ->
|
|
741
|
+
moduleScope.launch(Dispatchers.IO) {
|
|
742
|
+
try {
|
|
743
|
+
val context = appContext.reactContext ?: throw Exception("React context is not available")
|
|
744
|
+
val uri = Uri.parse(contentUri)
|
|
745
|
+
|
|
746
|
+
if (uri.scheme != "content") {
|
|
747
|
+
throw Exception("Only content:// URIs are supported. Received: ${uri.scheme}://")
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Get metadata from ContentResolver
|
|
751
|
+
val metadata = getContentUriMetadataInternal(context, uri)
|
|
752
|
+
|
|
753
|
+
// Determine file name
|
|
754
|
+
val finalFileName = fileName ?: metadata["displayName"] as? String
|
|
755
|
+
?: "file_${System.currentTimeMillis()}"
|
|
756
|
+
|
|
757
|
+
// Create destination file in cache directory
|
|
758
|
+
val cacheDir = context.cacheDir
|
|
759
|
+
val destFile = File(cacheDir, finalFileName)
|
|
760
|
+
|
|
761
|
+
// Copy file using ContentResolver InputStream
|
|
762
|
+
var inputStream: InputStream? = null
|
|
763
|
+
var outputStream: FileOutputStream? = null
|
|
764
|
+
var totalBytes = 0L
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
inputStream = context.contentResolver.openInputStream(uri)
|
|
768
|
+
?: throw Exception("Failed to open input stream for URI: $contentUri")
|
|
769
|
+
|
|
770
|
+
outputStream = FileOutputStream(destFile)
|
|
771
|
+
val buffer = ByteArray(8192)
|
|
772
|
+
var bytesRead: Int
|
|
773
|
+
|
|
774
|
+
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
|
775
|
+
outputStream.write(buffer, 0, bytesRead)
|
|
776
|
+
totalBytes += bytesRead
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
outputStream.flush()
|
|
780
|
+
|
|
781
|
+
// Build result
|
|
782
|
+
val result = mapOf(
|
|
783
|
+
"localPath" to destFile.absolutePath,
|
|
784
|
+
"fileName" to finalFileName,
|
|
785
|
+
"mimeType" to metadata["mimeType"],
|
|
786
|
+
"size" to totalBytes
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
withContext(Dispatchers.Main) { promise.resolve(result) }
|
|
790
|
+
} finally {
|
|
791
|
+
inputStream?.close()
|
|
792
|
+
outputStream?.close()
|
|
793
|
+
}
|
|
794
|
+
} catch (e: Exception) {
|
|
795
|
+
withContext(Dispatchers.Main) {
|
|
796
|
+
promise.reject("COPY_CONTENT_URI_ERROR", "Failed to copy content URI: ${e.message}", e)
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
AsyncFunction("getContentUriMetadata") { contentUri: String, promise: Promise ->
|
|
803
|
+
moduleScope.launch(Dispatchers.IO) {
|
|
804
|
+
try {
|
|
805
|
+
val context = appContext.reactContext ?: throw Exception("React context is not available")
|
|
806
|
+
val uri = Uri.parse(contentUri)
|
|
807
|
+
|
|
808
|
+
if (uri.scheme != "content") {
|
|
809
|
+
throw Exception("Only content:// URIs are supported. Received: ${uri.scheme}://")
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
val metadata = getContentUriMetadataInternal(context, uri)
|
|
813
|
+
withContext(Dispatchers.Main) { promise.resolve(metadata) }
|
|
814
|
+
} catch (e: Exception) {
|
|
815
|
+
withContext(Dispatchers.Main) {
|
|
816
|
+
promise.reject("GET_CONTENT_URI_METADATA_ERROR", "Failed to get metadata: ${e.message}", e)
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// TTS (Text-to-Speech) Function
|
|
823
|
+
AsyncFunction("synthesizeSpeech") { options: Map<String, Any?>, promise: Promise ->
|
|
824
|
+
moduleScope.launch(Dispatchers.IO) {
|
|
825
|
+
try {
|
|
826
|
+
val context = appContext.reactContext ?: throw Exception("React context is not available")
|
|
827
|
+
|
|
828
|
+
val text = options["text"] as? String ?: throw Exception("Text is required")
|
|
829
|
+
val voiceId = options["voiceId"] as? String
|
|
830
|
+
val language = options["language"] as? String ?: "en-US"
|
|
831
|
+
val pitch = (options["pitch"] as? Double)?.toFloat() ?: 1.0f
|
|
832
|
+
val rate = (options["rate"] as? Double)?.toFloat() ?: 1.0f
|
|
833
|
+
|
|
834
|
+
// Initialize TTS if not already initialized
|
|
835
|
+
if (textToSpeech == null) {
|
|
836
|
+
val latch = CompletableDeferred<Boolean>()
|
|
837
|
+
withContext(Dispatchers.Main) {
|
|
838
|
+
textToSpeech = TextToSpeech(context) { status ->
|
|
839
|
+
if (status == TextToSpeech.SUCCESS) {
|
|
840
|
+
ttsInitialized = true
|
|
841
|
+
latch.complete(true)
|
|
842
|
+
} else {
|
|
843
|
+
latch.completeExceptionally(Exception("TTS initialization failed"))
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
latch.await()
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Set language
|
|
851
|
+
val locale = parseLocale(language)
|
|
852
|
+
val result = textToSpeech?.setLanguage(locale)
|
|
853
|
+
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
|
|
854
|
+
throw Exception("Language not supported: $language")
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Set voice if specified
|
|
858
|
+
if (voiceId != null) {
|
|
859
|
+
val voices = textToSpeech?.voices
|
|
860
|
+
val voice = voices?.find { it.name == voiceId }
|
|
861
|
+
if (voice != null) {
|
|
862
|
+
textToSpeech?.voice = voice
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Set pitch and rate
|
|
867
|
+
textToSpeech?.setPitch(pitch)
|
|
868
|
+
textToSpeech?.setSpeechRate(rate)
|
|
869
|
+
|
|
870
|
+
// Create TTS cache directory
|
|
871
|
+
val ttsDir = File(context.cacheDir, "tts")
|
|
872
|
+
if (!ttsDir.exists()) {
|
|
873
|
+
ttsDir.mkdirs()
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Generate unique filename
|
|
877
|
+
val fileName = "tts_${UUID.randomUUID()}.wav"
|
|
878
|
+
val outputFile = File(ttsDir, fileName)
|
|
879
|
+
|
|
880
|
+
// Synthesize to file
|
|
881
|
+
val utteranceId = UUID.randomUUID().toString()
|
|
882
|
+
val synthesisLatch = CompletableDeferred<String>()
|
|
883
|
+
|
|
884
|
+
withContext<Unit>(Dispatchers.Main) {
|
|
885
|
+
textToSpeech?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
|
|
886
|
+
override fun onStart(uId: String?) {}
|
|
887
|
+
|
|
888
|
+
override fun onDone(id: String?) {
|
|
889
|
+
if (id == utteranceId) {
|
|
890
|
+
synthesisLatch.complete(outputFile.absolutePath)
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
@Deprecated("Deprecated in Java", ReplaceWith("onError(String)"))
|
|
895
|
+
override fun onError(uId: String?) {
|
|
896
|
+
synthesisLatch.completeExceptionally(Exception("TTS synthesis failed"))
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
override fun onError(uId: String?, errorCode: Int) {
|
|
900
|
+
synthesisLatch.completeExceptionally(Exception("TTS synthesis failed with code: $errorCode"))
|
|
901
|
+
}
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
val params = Bundle().apply {
|
|
905
|
+
putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId)
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
@Suppress("DEPRECATION")
|
|
909
|
+
textToSpeech?.synthesizeToFile(text, params, outputFile, utteranceId)
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
val filePath = synthesisLatch.await()
|
|
913
|
+
|
|
914
|
+
// Get file duration (approximate based on text length and rate)
|
|
915
|
+
val duration = estimateDuration(text, rate)
|
|
916
|
+
|
|
917
|
+
withContext(Dispatchers.Main) {
|
|
918
|
+
promise.resolve(mapOf(
|
|
919
|
+
"filePath" to filePath,
|
|
920
|
+
"duration" to duration
|
|
921
|
+
))
|
|
922
|
+
}
|
|
923
|
+
} catch (e: Exception) {
|
|
924
|
+
withContext(Dispatchers.Main) {
|
|
925
|
+
promise.reject("TTS_SYNTHESIS_ERROR", "Failed to synthesize speech: ${e.message}", e)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Cleanup when module is destroyed
|
|
932
|
+
OnDestroy {
|
|
933
|
+
textToSpeech?.shutdown()
|
|
934
|
+
textToSpeech = null
|
|
935
|
+
}
|
|
936
|
+
|
|
726
937
|
// Enables the module to be used as a native view. Definition components that are accepted as part of
|
|
727
938
|
// the view definition: Prop, Events.
|
|
728
939
|
View(ReactNativeKookitView::class) {
|
|
@@ -789,4 +1000,77 @@ class ReactNativeKookitModule : Module() {
|
|
|
789
1000
|
// Context might be lost, ignore
|
|
790
1001
|
}
|
|
791
1002
|
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Internal helper to query content URI metadata using ContentResolver
|
|
1006
|
+
*/
|
|
1007
|
+
private fun getContentUriMetadataInternal(context: Context, uri: Uri): Map<String, Any?> {
|
|
1008
|
+
var cursor: Cursor? = null
|
|
1009
|
+
try {
|
|
1010
|
+
cursor = context.contentResolver.query(
|
|
1011
|
+
uri,
|
|
1012
|
+
arrayOf(
|
|
1013
|
+
OpenableColumns.DISPLAY_NAME,
|
|
1014
|
+
OpenableColumns.SIZE
|
|
1015
|
+
),
|
|
1016
|
+
null,
|
|
1017
|
+
null,
|
|
1018
|
+
null
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
var displayName: String? = null
|
|
1022
|
+
var size: Long? = null
|
|
1023
|
+
|
|
1024
|
+
cursor?.use {
|
|
1025
|
+
if (it.moveToFirst()) {
|
|
1026
|
+
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
1027
|
+
val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
|
|
1028
|
+
|
|
1029
|
+
if (nameIndex != -1) {
|
|
1030
|
+
displayName = it.getString(nameIndex)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (sizeIndex != -1 && !it.isNull(sizeIndex)) {
|
|
1034
|
+
size = it.getLong(sizeIndex)
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Get MIME type
|
|
1040
|
+
val mimeType = context.contentResolver.getType(uri)
|
|
1041
|
+
|
|
1042
|
+
return mapOf(
|
|
1043
|
+
"displayName" to displayName,
|
|
1044
|
+
"mimeType" to mimeType,
|
|
1045
|
+
"size" to size
|
|
1046
|
+
)
|
|
1047
|
+
} catch (e: Exception) {
|
|
1048
|
+
throw Exception("Failed to query content URI metadata: ${e.message}", e)
|
|
1049
|
+
} finally {
|
|
1050
|
+
cursor?.close()
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Parse locale string (e.g., "en-US", "zh-CN") to Locale object
|
|
1056
|
+
*/
|
|
1057
|
+
private fun parseLocale(localeString: String): Locale {
|
|
1058
|
+
val parts = localeString.split("-", "_")
|
|
1059
|
+
return when (parts.size) {
|
|
1060
|
+
1 -> Locale(parts[0])
|
|
1061
|
+
2 -> Locale(parts[0], parts[1])
|
|
1062
|
+
else -> Locale(parts[0], parts[1], parts[2])
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Estimate audio duration based on text length and speech rate
|
|
1068
|
+
* This is a rough estimation: average speaking rate is about 150 words per minute
|
|
1069
|
+
*/
|
|
1070
|
+
private fun estimateDuration(text: String, rate: Float): Double {
|
|
1071
|
+
val wordCount = text.split("\\s+".toRegex()).size
|
|
1072
|
+
val baseMinutes = wordCount / 150.0
|
|
1073
|
+
val durationSeconds = (baseMinutes * 60.0) / rate
|
|
1074
|
+
return durationSeconds
|
|
1075
|
+
}
|
|
792
1076
|
}
|
|
@@ -96,4 +96,15 @@ export type SmbEventPayload = {
|
|
|
96
96
|
onComplete?: () => void;
|
|
97
97
|
onError?: (error: string) => void;
|
|
98
98
|
};
|
|
99
|
+
export type TtsSynthesizeOptions = {
|
|
100
|
+
text: string;
|
|
101
|
+
voiceId?: string;
|
|
102
|
+
language?: string;
|
|
103
|
+
pitch?: number;
|
|
104
|
+
rate?: number;
|
|
105
|
+
};
|
|
106
|
+
export type TtsSynthesizeResult = {
|
|
107
|
+
filePath: string;
|
|
108
|
+
duration?: number;
|
|
109
|
+
};
|
|
99
110
|
//# sourceMappingURL=ReactNativeKookit.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeKookit.types.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReactNativeKookit.types.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeKookit.types.js","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from \"react-native\";\n\nexport type OnLoadEventPayload = {\n url: string;\n};\n\nexport type VolumeKeyEventPayload = {\n key: \"up\" | \"down\";\n};\n\nexport type FtpConnectionConfig = {\n host: string;\n port?: number;\n username: string;\n password: string;\n passive?: boolean;\n timeout?: number;\n};\n\nexport type FtpFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified: string;\n permissions?: string;\n};\n\nexport type FtpProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type FtpProgressEvent = FtpProgressInfo & {\n clientId: string;\n};\n\nexport type FtpCompleteEvent = {\n clientId: string;\n};\n\nexport type FtpErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type FtpEventPayload = {\n onProgress?: (progress: FtpProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n\nexport type ReactNativeKookitModuleEvents = {\n onChange: (params: ChangeEventPayload) => void;\n onVolumeButtonPressed: (params: VolumeKeyEventPayload) => void;\n onFtpProgress: (params: FtpProgressEvent) => void;\n onFtpComplete: (params: FtpCompleteEvent) => void;\n onFtpError: (params: FtpErrorEvent) => void;\n // SMB events (reserved for future file operations parity)\n onSmbProgress: (params: SmbProgressEvent) => void;\n onSmbComplete: (params: SmbCompleteEvent) => void;\n onSmbError: (params: SmbErrorEvent) => void;\n};\n\nexport type ChangeEventPayload = {\n value: string;\n};\n\nexport type ReactNativeKookitViewProps = {\n url: string;\n onLoad: (event: { nativeEvent: OnLoadEventPayload }) => void;\n style?: StyleProp<ViewStyle>;\n};\n\n// SMB API parity with FTP (OOP new-client style)\nexport type SmbConnectionConfig = {\n host: string;\n port?: number; // default 445\n username: string;\n password: string;\n domain?: string; // optional Windows domain or workgroup\n share?: string; // optional default share to open\n timeout?: number; // ms\n};\n\nexport type SmbFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified?: string;\n attributes?: string;\n};\n\nexport type SmbProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type SmbProgressEvent = SmbProgressInfo & {\n clientId: string;\n};\n\nexport type SmbCompleteEvent = {\n clientId: string;\n};\n\nexport type SmbErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type SmbEventPayload = {\n onProgress?: (progress: SmbProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"ReactNativeKookit.types.js","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from \"react-native\";\n\nexport type OnLoadEventPayload = {\n url: string;\n};\n\nexport type VolumeKeyEventPayload = {\n key: \"up\" | \"down\";\n};\n\nexport type FtpConnectionConfig = {\n host: string;\n port?: number;\n username: string;\n password: string;\n passive?: boolean;\n timeout?: number;\n};\n\nexport type FtpFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified: string;\n permissions?: string;\n};\n\nexport type FtpProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type FtpProgressEvent = FtpProgressInfo & {\n clientId: string;\n};\n\nexport type FtpCompleteEvent = {\n clientId: string;\n};\n\nexport type FtpErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type FtpEventPayload = {\n onProgress?: (progress: FtpProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n\nexport type ReactNativeKookitModuleEvents = {\n onChange: (params: ChangeEventPayload) => void;\n onVolumeButtonPressed: (params: VolumeKeyEventPayload) => void;\n onFtpProgress: (params: FtpProgressEvent) => void;\n onFtpComplete: (params: FtpCompleteEvent) => void;\n onFtpError: (params: FtpErrorEvent) => void;\n // SMB events (reserved for future file operations parity)\n onSmbProgress: (params: SmbProgressEvent) => void;\n onSmbComplete: (params: SmbCompleteEvent) => void;\n onSmbError: (params: SmbErrorEvent) => void;\n};\n\nexport type ChangeEventPayload = {\n value: string;\n};\n\nexport type ReactNativeKookitViewProps = {\n url: string;\n onLoad: (event: { nativeEvent: OnLoadEventPayload }) => void;\n style?: StyleProp<ViewStyle>;\n};\n\n// SMB API parity with FTP (OOP new-client style)\nexport type SmbConnectionConfig = {\n host: string;\n port?: number; // default 445\n username: string;\n password: string;\n domain?: string; // optional Windows domain or workgroup\n share?: string; // optional default share to open\n timeout?: number; // ms\n};\n\nexport type SmbFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified?: string;\n attributes?: string;\n};\n\nexport type SmbProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type SmbProgressEvent = SmbProgressInfo & {\n clientId: string;\n};\n\nexport type SmbCompleteEvent = {\n clientId: string;\n};\n\nexport type SmbErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type SmbEventPayload = {\n onProgress?: (progress: SmbProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n\n// TTS (Text-to-Speech) API\nexport type TtsSynthesizeOptions = {\n text: string;\n voiceId?: string; // Voice identifier (platform-specific)\n language?: string; // Language code (e.g., 'en-US', 'zh-CN')\n pitch?: number; // Pitch (0.5 - 2.0, default: 1.0)\n rate?: number; // Speech rate (0.5 - 2.0, default: 1.0)\n};\n\nexport type TtsSynthesizeResult = {\n filePath: string; // Path to the generated audio file\n duration?: number; // Duration in seconds (if available)\n};\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NativeModule } from "expo";
|
|
2
|
-
import { ReactNativeKookitModuleEvents, FtpConnectionConfig, FtpFileInfo, SmbConnectionConfig, SmbFileInfo } from "./ReactNativeKookit.types";
|
|
2
|
+
import { ReactNativeKookitModuleEvents, FtpConnectionConfig, FtpFileInfo, SmbConnectionConfig, SmbFileInfo, TtsSynthesizeOptions, TtsSynthesizeResult } from "./ReactNativeKookit.types";
|
|
3
3
|
declare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModuleEvents> {
|
|
4
4
|
PI: number;
|
|
5
5
|
/**
|
|
@@ -141,6 +141,42 @@ declare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModu
|
|
|
141
141
|
}>;
|
|
142
142
|
count: number;
|
|
143
143
|
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Copy a file from content:// URI to local cache directory.
|
|
146
|
+
* This method uses Android's ContentResolver to read the file stream,
|
|
147
|
+
* bypassing permission restrictions when receiving content URIs from other apps.
|
|
148
|
+
*
|
|
149
|
+
* @param contentUri The content:// URI received from another app (e.g., via Intent)
|
|
150
|
+
* @param fileName Optional filename for the destination file. If not provided, tries to extract from URI
|
|
151
|
+
* @returns Promise that resolves with the local file path (file://)
|
|
152
|
+
* @platform android
|
|
153
|
+
*/
|
|
154
|
+
copyContentUriToCache(contentUri: string, fileName?: string): Promise<{
|
|
155
|
+
localPath: string;
|
|
156
|
+
fileName: string;
|
|
157
|
+
mimeType?: string;
|
|
158
|
+
size?: number;
|
|
159
|
+
}>;
|
|
160
|
+
/**
|
|
161
|
+
* Get metadata about a content:// URI without copying the file.
|
|
162
|
+
*
|
|
163
|
+
* @param contentUri The content:// URI to query
|
|
164
|
+
* @returns Promise that resolves with file metadata
|
|
165
|
+
* @platform android
|
|
166
|
+
*/
|
|
167
|
+
getContentUriMetadata(contentUri: string): Promise<{
|
|
168
|
+
displayName?: string;
|
|
169
|
+
mimeType?: string;
|
|
170
|
+
size?: number;
|
|
171
|
+
}>;
|
|
172
|
+
/**
|
|
173
|
+
* Synthesize speech from text and save to audio file.
|
|
174
|
+
* The audio file will be saved in the cache directory under 'tts' folder.
|
|
175
|
+
*
|
|
176
|
+
* @param options TTS synthesis options including text, voiceId, language, pitch, and rate
|
|
177
|
+
* @returns Promise that resolves with file path and duration of the generated audio
|
|
178
|
+
*/
|
|
179
|
+
synthesizeSpeech(options: TtsSynthesizeOptions): Promise<TtsSynthesizeResult>;
|
|
144
180
|
}
|
|
145
181
|
declare const _default: ReactNativeKookitModule;
|
|
146
182
|
export default _default;
|