react-native-kookit 0.3.8 → 0.4.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/README.md +31 -0
- package/android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt +8 -1
- package/build/ReactNativeKookitModule.d.ts +20 -0
- package/build/ReactNativeKookitModule.d.ts.map +1 -1
- package/build/ReactNativeKookitModule.js.map +1 -1
- package/ios/ReactNativeKookitModule.swift +61 -0
- package/package.json +9 -4
- package/CHANGELOG_CONTENT_URI.md +0 -0
- package/IMPLEMENTATION_SUMMARY.md +0 -0
- package/TTS_CHECKLIST.md +0 -256
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A comprehensive React Native/Expo module with multiple utilities for Android and
|
|
|
8
8
|
- 📁 **FTP Client** - Full-featured FTP client with upload/download capabilities
|
|
9
9
|
- 🗂️ **SMB Client** - Access SMB/CIFS network shares
|
|
10
10
|
- 📱 **Content URI Handler** (Android) - Handle `content://` URIs from other apps, bypassing permission restrictions
|
|
11
|
+
- 🔊 **Audio Detection** (Android) - Detect if any audio is currently playing (music, TTS, videos, etc.)
|
|
11
12
|
|
|
12
13
|
### Content URI Handler
|
|
13
14
|
|
|
@@ -23,6 +24,36 @@ The Content URI Handler solves a common Android problem: when receiving files vi
|
|
|
23
24
|
|
|
24
25
|
📖 [**View Content URI Handler Documentation**](./docs/CONTENT_URI_HANDLER.md)
|
|
25
26
|
|
|
27
|
+
### Audio Detection
|
|
28
|
+
|
|
29
|
+
The Audio Detection API allows you to check if the device is currently playing any audio, including music, TTS, videos, or any other audio source.
|
|
30
|
+
|
|
31
|
+
**Key Benefits:**
|
|
32
|
+
|
|
33
|
+
- ✅ Detect any audio playback (music, TTS, videos, games)
|
|
34
|
+
- ✅ Simple synchronous API
|
|
35
|
+
- ✅ No permissions required
|
|
36
|
+
- ✅ Lightweight and fast
|
|
37
|
+
- ✅ Perfect for audio queue management
|
|
38
|
+
|
|
39
|
+
**Quick Example:**
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import ReactNativeKookit from "react-native-kookit";
|
|
43
|
+
|
|
44
|
+
// Check if audio is playing
|
|
45
|
+
const isPlaying = ReactNativeKookit.isAudioPlaying();
|
|
46
|
+
console.log("Audio playing:", isPlaying);
|
|
47
|
+
|
|
48
|
+
// Monitor audio state
|
|
49
|
+
setInterval(() => {
|
|
50
|
+
const playing = ReactNativeKookit.isAudioPlaying();
|
|
51
|
+
console.log("Audio status:", playing ? "Playing" : "Idle");
|
|
52
|
+
}, 500);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
📖 [**View Audio Detection Documentation**](./docs/AUDIO_DETECTION.md)
|
|
56
|
+
|
|
26
57
|
# API documentation
|
|
27
58
|
|
|
28
59
|
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/react-native-kookit/)
|
|
@@ -928,6 +928,13 @@ class ReactNativeKookitModule : Module() {
|
|
|
928
928
|
}
|
|
929
929
|
}
|
|
930
930
|
|
|
931
|
+
// Check if audio is currently playing
|
|
932
|
+
Function("isAudioPlaying") {
|
|
933
|
+
val context = appContext.reactContext ?: return@Function false
|
|
934
|
+
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
|
|
935
|
+
audioManager?.isMusicActive ?: false
|
|
936
|
+
}
|
|
937
|
+
|
|
931
938
|
// Cleanup when module is destroyed
|
|
932
939
|
OnDestroy {
|
|
933
940
|
textToSpeech?.shutdown()
|
|
@@ -963,7 +970,7 @@ class ReactNativeKookitModule : Module() {
|
|
|
963
970
|
// Throw an error with helpful message
|
|
964
971
|
throw IllegalStateException(
|
|
965
972
|
"Your MainActivity must implement VolumeKeyInterceptActivity interface and override dispatchKeyEvent method. " +
|
|
966
|
-
"Please see the documentation
|
|
973
|
+
"Please see the documentation for setup instructions."
|
|
967
974
|
)
|
|
968
975
|
}
|
|
969
976
|
|
|
@@ -177,6 +177,26 @@ declare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModu
|
|
|
177
177
|
* @returns Promise that resolves with file path and duration of the generated audio
|
|
178
178
|
*/
|
|
179
179
|
synthesizeSpeech(options: TtsSynthesizeOptions): Promise<TtsSynthesizeResult>;
|
|
180
|
+
/**
|
|
181
|
+
* Copy a file with security-scoped resource access (iOS only).
|
|
182
|
+
* Required for accessing files passed via "Open With" from external apps.
|
|
183
|
+
*
|
|
184
|
+
* @param sourceUri The source file URI (file:// or path)
|
|
185
|
+
* @param destinationPath The destination file path
|
|
186
|
+
* @returns Promise that resolves with the local path and success status
|
|
187
|
+
*/
|
|
188
|
+
copySecurityScopedFile(sourceUri: string, destinationPath: string): Promise<{
|
|
189
|
+
localPath: string;
|
|
190
|
+
success: boolean;
|
|
191
|
+
}>;
|
|
192
|
+
/**
|
|
193
|
+
* Check if the device is currently playing any audio (music, TTS, or other audio).
|
|
194
|
+
* Uses AudioManager.isMusicActive() on Android which detects any audio playback.
|
|
195
|
+
*
|
|
196
|
+
* @returns Boolean indicating whether audio is currently playing
|
|
197
|
+
* @platform android
|
|
198
|
+
*/
|
|
199
|
+
isAudioPlaying(): boolean;
|
|
180
200
|
}
|
|
181
201
|
declare const _default: ReactNativeKookitModule;
|
|
182
202
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeKookitModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ReactNativeKookitModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeKookitModule.js","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ReactNativeKookitModule.js","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAsQzD,yDAAyD;AACzD,eAAe,mBAAmB,CAChC,mBAAmB,CACpB,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport {\n ReactNativeKookitModuleEvents,\n FtpConnectionConfig,\n FtpFileInfo,\n SmbConnectionConfig,\n SmbFileInfo,\n TtsSynthesizeOptions,\n TtsSynthesizeResult,\n} from \"./ReactNativeKookit.types\";\n\ndeclare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModuleEvents> {\n PI: number;\n\n /**\n * Returns a hello world string\n */\n hello(): string;\n\n /**\n * Test async function that sends a change event\n */\n setValueAsync(value: string): Promise<void>;\n\n /**\n * Enables volume key interception.\n * On Android, your MainActivity must implement VolumeKeyInterceptActivity interface.\n * On iOS, this works automatically.\n *\n * @throws Error if MainActivity doesn't implement VolumeKeyInterceptActivity on Android\n */\n enableVolumeKeyInterception(): void;\n\n /**\n * Disables volume key interception\n */\n disableVolumeKeyInterception(): void;\n\n // New FTP Client API Methods\n\n /**\n * Create a new FTP client instance\n * @param clientId Unique identifier for the FTP client\n * @returns Promise that resolves with client creation result\n */\n createFtpClient(clientId: string): Promise<{ clientId: string }>;\n\n /**\n * Connect FTP client to server\n * @param clientId FTP client identifier\n * @param config FTP connection configuration\n * @returns Promise that resolves when connected\n */\n ftpClientConnect(\n clientId: string,\n config: FtpConnectionConfig,\n ): Promise<void>;\n\n /**\n * Disconnect FTP client from server\n * @param clientId FTP client identifier\n * @returns Promise that resolves when disconnected\n */\n ftpClientDisconnect(clientId: string): Promise<void>;\n\n /**\n * Dispose FTP client and clean up resources\n * @param clientId FTP client identifier\n * @returns Promise that resolves when disposed\n */\n disposeFtpClient(clientId: string): Promise<void>;\n\n /**\n * List files and directories\n * @param clientId FTP client identifier\n * @param path Optional path to list (defaults to current directory)\n * @returns Promise that resolves with array of file information\n */\n ftpClientList(clientId: string, path?: string): Promise<FtpFileInfo[]>;\n\n /**\n * Download a file from FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote file path on FTP server\n * @param localPath Local file path to save the downloaded file\n * @returns Promise that resolves when download is complete\n */\n ftpClientDownload(\n clientId: string,\n remotePath: string,\n localPath: string,\n ): Promise<void>;\n\n /**\n * Upload a file to FTP server\n * @param clientId FTP client identifier\n * @param localPath Local file path to upload\n * @param remotePath Remote file path on FTP server\n * @returns Promise that resolves when upload is complete\n */\n ftpClientUpload(\n clientId: string,\n localPath: string,\n remotePath: string,\n ): Promise<void>;\n\n /**\n * Delete a file or directory on FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote file or directory path to delete\n * @param isDirectory Whether the path is a directory (default: false)\n * @returns Promise that resolves when deletion is complete\n */\n ftpClientDelete(\n clientId: string,\n remotePath: string,\n isDirectory?: boolean,\n ): Promise<void>;\n\n /**\n * Create a directory on FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote directory path to create\n * @returns Promise that resolves when directory is created\n */\n ftpClientCreateDirectory(clientId: string, remotePath: string): Promise<void>;\n\n /**\n * Change current working directory on FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote directory path to change to\n * @returns Promise that resolves when directory is changed\n */\n ftpClientChangeDirectory(clientId: string, remotePath: string): Promise<void>;\n\n /**\n * Get current working directory on FTP server\n * @param clientId FTP client identifier\n * @returns Promise that resolves with current directory path\n */\n ftpClientGetCurrentDirectory(clientId: string): Promise<string>;\n\n /**\n * Get FTP client status\n * @param clientId FTP client identifier\n * @returns Client status information\n */\n getFtpClientStatus(\n clientId: string,\n ): Promise<{ exists: boolean; connected: boolean }>;\n\n /**\n * List all FTP clients\n * @returns Object containing all clients and their status\n */\n listFtpClients(): Promise<{\n clients: Record<string, { connected: boolean }>;\n count: number;\n }>;\n\n // SMB Client API\n\n createSmbClient(clientId: string): Promise<{ clientId: string }>;\n smbClientConnect(\n clientId: string,\n config: SmbConnectionConfig,\n ): Promise<void>;\n smbClientConnectShare(clientId: string, shareName: string): Promise<void>;\n smbClientDisconnect(clientId: string): Promise<void>;\n disposeSmbClient(clientId: string): Promise<void>;\n smbClientList(clientId: string, path?: string): Promise<SmbFileInfo[]>;\n smbClientDownload(\n clientId: string,\n remotePath: string,\n localPath: string,\n ): Promise<void>;\n smbClientUpload(\n clientId: string,\n localPath: string,\n remotePath: string,\n ): Promise<void>;\n smbClientDelete(\n clientId: string,\n remotePath: string,\n isDirectory?: boolean,\n ): Promise<void>;\n smbClientCreateDirectory(clientId: string, remotePath: string): Promise<void>;\n getSmbClientStatus(\n clientId: string,\n ): Promise<{ exists: boolean; connected: boolean }>;\n listSmbClients(): Promise<{\n clients: Record<string, { connected: boolean }>;\n count: number;\n }>;\n\n /**\n * Copy a file from content:// URI to local cache directory.\n * This method uses Android's ContentResolver to read the file stream,\n * bypassing permission restrictions when receiving content URIs from other apps.\n *\n * @param contentUri The content:// URI received from another app (e.g., via Intent)\n * @param fileName Optional filename for the destination file. If not provided, tries to extract from URI\n * @returns Promise that resolves with the local file path (file://)\n * @platform android\n */\n copyContentUriToCache(\n contentUri: string,\n fileName?: string,\n ): Promise<{\n localPath: string;\n fileName: string;\n mimeType?: string;\n size?: number;\n }>;\n\n /**\n * Get metadata about a content:// URI without copying the file.\n *\n * @param contentUri The content:// URI to query\n * @returns Promise that resolves with file metadata\n * @platform android\n */\n getContentUriMetadata(contentUri: string): Promise<{\n displayName?: string;\n mimeType?: string;\n size?: number;\n }>;\n\n /**\n * Synthesize speech from text and save to audio file.\n * The audio file will be saved in the cache directory under 'tts' folder.\n *\n * @param options TTS synthesis options including text, voiceId, language, pitch, and rate\n * @returns Promise that resolves with file path and duration of the generated audio\n */\n synthesizeSpeech(options: TtsSynthesizeOptions): Promise<TtsSynthesizeResult>;\n /**\n * Copy a file with security-scoped resource access (iOS only).\n * Required for accessing files passed via \"Open With\" from external apps.\n *\n * @param sourceUri The source file URI (file:// or path)\n * @param destinationPath The destination file path\n * @returns Promise that resolves with the local path and success status\n */\n copySecurityScopedFile(\n sourceUri: string,\n destinationPath: string,\n ): Promise<{\n localPath: string;\n success: boolean;\n }>;\n /**\n * Check if the device is currently playing any audio (music, TTS, or other audio).\n * Uses AudioManager.isMusicActive() on Android which detects any audio playback.\n *\n * @returns Boolean indicating whether audio is currently playing\n * @platform android\n */\n isAudioPlaying(): boolean;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<ReactNativeKookitModule>(\n \"ReactNativeKookit\",\n);\n"]}
|
|
@@ -1548,6 +1548,67 @@ public class ReactNativeKookitModule: Module {
|
|
|
1548
1548
|
}
|
|
1549
1549
|
}
|
|
1550
1550
|
}
|
|
1551
|
+
// Copy file with security-scoped resource access (for iOS "Open With")
|
|
1552
|
+
AsyncFunction("copySecurityScopedFile") { (sourceUri: String, destinationPath: String, promise: Promise) in
|
|
1553
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
1554
|
+
do {
|
|
1555
|
+
// Parse source URL
|
|
1556
|
+
guard let sourceURL = URL(string: sourceUri) ?? URL(fileURLWithPath: sourceUri.replacingOccurrences(of: "file://", with: "")) else {
|
|
1557
|
+
promise.reject("COPY_ERROR", "Invalid source URI: \(sourceUri)")
|
|
1558
|
+
return
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
let destURL = URL(fileURLWithPath: destinationPath.replacingOccurrences(of: "file://", with: ""))
|
|
1562
|
+
|
|
1563
|
+
// Ensure destination directory exists
|
|
1564
|
+
let destDir = destURL.deletingLastPathComponent()
|
|
1565
|
+
try FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: true)
|
|
1566
|
+
|
|
1567
|
+
// Remove existing file at destination
|
|
1568
|
+
if FileManager.default.fileExists(atPath: destURL.path) {
|
|
1569
|
+
try FileManager.default.removeItem(at: destURL)
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Start accessing security-scoped resource
|
|
1573
|
+
let didStartAccessing = sourceURL.startAccessingSecurityScopedResource()
|
|
1574
|
+
defer {
|
|
1575
|
+
if didStartAccessing {
|
|
1576
|
+
sourceURL.stopAccessingSecurityScopedResource()
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Use NSFileCoordinator for coordinated read
|
|
1581
|
+
var coordinatorError: NSError?
|
|
1582
|
+
var copyError: Error?
|
|
1583
|
+
let coordinator = NSFileCoordinator()
|
|
1584
|
+
|
|
1585
|
+
coordinator.coordinate(readingItemAt: sourceURL, options: [], error: &coordinatorError) { coordinatedURL in
|
|
1586
|
+
do {
|
|
1587
|
+
try FileManager.default.copyItem(at: coordinatedURL, to: destURL)
|
|
1588
|
+
} catch {
|
|
1589
|
+
copyError = error
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
if let error = coordinatorError {
|
|
1594
|
+
promise.reject("COPY_ERROR", "File coordination failed: \(error.localizedDescription)")
|
|
1595
|
+
return
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
if let error = copyError {
|
|
1599
|
+
promise.reject("COPY_ERROR", "File copy failed: \(error.localizedDescription)")
|
|
1600
|
+
return
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
promise.resolve([
|
|
1604
|
+
"localPath": destURL.path,
|
|
1605
|
+
"success": true
|
|
1606
|
+
])
|
|
1607
|
+
} catch {
|
|
1608
|
+
promise.reject("COPY_ERROR", "Unexpected error: \(error.localizedDescription)")
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1551
1612
|
|
|
1552
1613
|
// TTS (Text-to-Speech) Function
|
|
1553
1614
|
AsyncFunction("synthesizeSpeech") { (options: [String: Any], promise: Promise) in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-kookit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React Native module for intercepting volume button presses on iOS and Android, with FTP client functionality",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -31,12 +31,14 @@
|
|
|
31
31
|
"react-native-kookit",
|
|
32
32
|
"ReactNativeKookit"
|
|
33
33
|
],
|
|
34
|
-
"repository":
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/troyeguo/react-native-kookit.git"
|
|
37
|
+
},
|
|
35
38
|
"bugs": {
|
|
36
39
|
"url": "https://github.com/troyeguo/react-native-kookit/issues"
|
|
37
40
|
},
|
|
38
41
|
"author": "troyeguo <13820674+troyeguo@users.noreply.github.com> (https://github.com/troyeguo)",
|
|
39
|
-
"license": "MIT",
|
|
40
42
|
"homepage": "https://github.com/troyeguo/react-native-kookit#readme",
|
|
41
43
|
"files": [
|
|
42
44
|
"build",
|
|
@@ -49,7 +51,6 @@
|
|
|
49
51
|
"plugin/package.json",
|
|
50
52
|
"*.md"
|
|
51
53
|
],
|
|
52
|
-
"dependencies": {},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@expo/config-plugins": "^8.0.0",
|
|
55
56
|
"@types/react": "~19.0.0",
|
|
@@ -68,5 +69,9 @@
|
|
|
68
69
|
"configPlugins": [
|
|
69
70
|
"./app.plugin.js"
|
|
70
71
|
]
|
|
72
|
+
},
|
|
73
|
+
"directories": {
|
|
74
|
+
"doc": "docs",
|
|
75
|
+
"example": "example"
|
|
71
76
|
}
|
|
72
77
|
}
|
package/CHANGELOG_CONTENT_URI.md
DELETED
|
File without changes
|
|
File without changes
|
package/TTS_CHECKLIST.md
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
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
|
-
**状态**: 🎉 实现完成,可以使用!
|