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 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 at https://github.com/troyeguo/react-native-kookit for setup instructions."
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;AA+OzD,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\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<ReactNativeKookitModule>(\n \"ReactNativeKookit\"\n);\n"]}
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.8",
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": "https://github.com/troyeguo/react-native-kookit",
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
  }
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
- **状态**: 🎉 实现完成,可以使用!