react-native-kookit 0.3.7 → 0.3.9

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()
@@ -177,6 +177,14 @@ 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
+ * Check if the device is currently playing any audio (music, TTS, or other audio).
182
+ * Uses AudioManager.isMusicActive() on Android which detects any audio playback.
183
+ *
184
+ * @returns Boolean indicating whether audio is currently playing
185
+ * @platform android
186
+ */
187
+ isAudioPlaying(): boolean;
180
188
  }
181
189
  declare const _default: ReactNativeKookitModule;
182
190
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeKookitModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,OAAO,uBAAwB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;IACvF,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,KAAK,IAAI,MAAM;IAEf;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3C;;;;;;OAMG;IACH,2BAA2B,IAAI,IAAI;IAEnC;;OAEG;IACH,4BAA4B,IAAI,IAAI;IAIpC;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAEhE;;;;;OAKG;IACH,gBAAgB,CACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC;IAEhB;;;;OAIG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEpD;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEjD;;;;;OAKG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAEtE;;;;;;OAMG;IACH,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAEhB;;;;;;OAMG;IACH,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAEhB;;;;;;OAMG;IACH,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,IAAI,CAAC;IAEhB;;;;;OAKG;IACH,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7E;;;;;OAKG;IACH,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7E;;;;OAIG;IACH,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAE/D;;;;OAIG;IACH,kBAAkB,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAEnD;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAChD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAIF,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,gBAAgB,CACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC;IAChB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACzE,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACpD,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACjD,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACtE,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAChB,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAChB,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,IAAI,CAAC;IAChB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7E,kBAAkB,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IACnD,cAAc,IAAI,OAAO,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAChD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAEF;;;;;;;;;OASG;IACH,qBAAqB,CACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QACT,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAEF;;;;;;OAMG;IACH,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QACjD,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IAEF;;;;;;OAMG;IACH,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAC9E;;AAGD,wBAEE"}
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;AAwPzD,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 * 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"]}
@@ -1551,25 +1551,41 @@ public class ReactNativeKookitModule: Module {
1551
1551
 
1552
1552
  // TTS (Text-to-Speech) Function
1553
1553
  AsyncFunction("synthesizeSpeech") { (options: [String: Any], promise: Promise) in
1554
- Task {
1555
- do {
1556
- let text = options["text"] as? String ?? ""
1557
- if text.isEmpty {
1558
- promise.reject("TTS_ERROR", "Text is required")
1559
- return
1560
- }
1561
-
1562
- let voiceId = options["voiceId"] as? String
1563
- let language = options["language"] as? String ?? "en-US"
1564
- let pitch = options["pitch"] as? Double ?? 1.0
1565
- let rate = options["rate"] as? Double ?? 1.0
1566
-
1567
- // Initialize synthesizer if needed
1554
+ // Extract parameters
1555
+ let text = options["text"] as? String ?? ""
1556
+ if text.isEmpty {
1557
+ promise.reject("TTS_ERROR", "Text is required")
1558
+ return
1559
+ }
1560
+
1561
+ let voiceId = options["voiceId"] as? String
1562
+ let language = options["language"] as? String ?? "en-US"
1563
+ let pitch = options["pitch"] as? Double ?? 1.0
1564
+ let rate = options["rate"] as? Double ?? 1.0
1565
+ //print the voiceId
1566
+ print("voiceId: \(voiceId)")
1567
+ //print the language
1568
+ print("language: \(language)")
1569
+ //print the pitch
1570
+ print("pitch: \(pitch)")
1571
+ //print the rate
1572
+ print("rate: \(rate)")
1573
+
1574
+ // Initialize synthesizer on main thread if needed
1575
+ if self.speechSynthesizer == nil {
1576
+ DispatchQueue.main.sync {
1568
1577
  if self.speechSynthesizer == nil {
1569
- self.speechSynthesizer = AVSpeechSynthesizer()
1578
+ let synth = AVSpeechSynthesizer()
1579
+ synth.usesApplicationAudioSession = true
1580
+ self.speechSynthesizer = synth
1570
1581
  }
1571
-
1572
- let filePath = try await self.synthesizeSpeechToFile(
1582
+ }
1583
+ }
1584
+
1585
+ // Perform synthesis on a background queue to avoid blocking
1586
+ DispatchQueue.global(qos: .userInitiated).async {
1587
+ do {
1588
+ let filePath = try self.synthesizeSpeechToFileSync(
1573
1589
  text: text,
1574
1590
  voiceId: voiceId,
1575
1591
  language: language,
@@ -1686,78 +1702,126 @@ public class ReactNativeKookitModule: Module {
1686
1702
 
1687
1703
  // MARK: - TTS Helper Methods
1688
1704
 
1689
- private func synthesizeSpeechToFile(text: String, voiceId: String?, language: String, pitch: Float, rate: Float) async throws -> String {
1690
- return try await withCheckedThrowingContinuation { continuation in
1691
- guard let synthesizer = self.speechSynthesizer else {
1692
- continuation.resume(throwing: NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "Speech synthesizer not initialized"]))
1693
- return
1694
- }
1695
-
1696
- let utterance = AVSpeechUtterance(string: text)
1697
-
1698
- // Set voice
1699
- if let voiceId = voiceId {
1700
- utterance.voice = AVSpeechSynthesisVoice(identifier: voiceId)
1705
+ // Synchronous version using write callback to generate audio file WITHOUT playing
1706
+ private func synthesizeSpeechToFileSync(text: String, voiceId: String?, language: String, pitch: Float, rate: Float) throws -> String {
1707
+ guard let synthesizer = self.speechSynthesizer else {
1708
+ throw NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "Speech synthesizer not initialized"])
1709
+ }
1710
+
1711
+ // Create utterance
1712
+ let utterance = AVSpeechUtterance(string: text)
1713
+
1714
+ // Set voice
1715
+ if let voiceId = voiceId {
1716
+ utterance.voice = AVSpeechSynthesisVoice(identifier: voiceId)
1717
+ } else {
1718
+ let voices = AVSpeechSynthesisVoice.speechVoices()
1719
+ if let voice = voices.first(where: { $0.language.hasPrefix(language) }) {
1720
+ utterance.voice = voice
1701
1721
  } else {
1702
- // Try to find voice by language
1703
- let voices = AVSpeechSynthesisVoice.speechVoices()
1704
- if let voice = voices.first(where: { $0.language.hasPrefix(language) }) {
1705
- utterance.voice = voice
1706
- } else {
1707
- utterance.voice = AVSpeechSynthesisVoice(language: language)
1708
- }
1722
+ utterance.voice = AVSpeechSynthesisVoice(language: language)
1723
+ }
1724
+ }
1725
+
1726
+ // Set pitch and rate
1727
+ utterance.pitchMultiplier = max(0.5, min(pitch, 2.0))
1728
+ let minRate = AVSpeechUtteranceMinimumSpeechRate
1729
+ let maxRate = AVSpeechUtteranceMaximumSpeechRate
1730
+ let targetRate = rate * AVSpeechUtteranceDefaultSpeechRate
1731
+ utterance.rate = min(max(targetRate, minRate), maxRate)
1732
+
1733
+ // Create TTS cache directory
1734
+ let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
1735
+ let ttsDir = cacheDir.appendingPathComponent("tts")
1736
+ try FileManager.default.createDirectory(at: ttsDir, withIntermediateDirectories: true, attributes: nil)
1737
+
1738
+ // Generate unique filename
1739
+ let fileName = "tts_\(UUID().uuidString).caf"
1740
+ let outputURL = ttsDir.appendingPathComponent(fileName)
1741
+
1742
+ // Synchronization
1743
+ let semaphore = DispatchSemaphore(value: 0)
1744
+ var audioFile: AVAudioFile?
1745
+ var capturedError: Error?
1746
+ var bufferCount = 0
1747
+
1748
+ print("🎤 Starting TTS audio generation (without playback)...")
1749
+
1750
+ // Use write callback to generate audio WITHOUT playing
1751
+ synthesizer.write(utterance) { buffer in
1752
+ // Check for completion: buffer with frameLength == 0 OR buffer is nil
1753
+ guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
1754
+ // buffer is nil or not PCM - this means completion
1755
+ print("✅ TTS generation completed, total buffers: \(bufferCount)")
1756
+ semaphore.signal()
1757
+ return
1709
1758
  }
1710
1759
 
1711
- // Set pitch and rate
1712
- utterance.pitchMultiplier = pitch
1713
- utterance.rate = rate * AVSpeechUtteranceDefaultSpeechRate
1714
-
1715
- // Create TTS cache directory
1716
- let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
1717
- let ttsDir = cacheDir.appendingPathComponent("tts")
1718
-
1719
- do {
1720
- try FileManager.default.createDirectory(at: ttsDir, withIntermediateDirectories: true, attributes: nil)
1721
- } catch {
1722
- continuation.resume(throwing: error)
1760
+ // Check if this is an empty buffer (completion signal)
1761
+ if pcmBuffer.frameLength == 0 {
1762
+ print("✅ TTS generation completed (empty buffer), total buffers: \(bufferCount)")
1763
+ semaphore.signal()
1723
1764
  return
1724
1765
  }
1725
1766
 
1726
- // Generate unique filename
1727
- let fileName = "tts_\(UUID().uuidString).caf"
1728
- let outputURL = ttsDir.appendingPathComponent(fileName)
1767
+ // Process non-empty buffer
1768
+ bufferCount += 1
1729
1769
 
1730
- // Write utterance to file using AVSpeechSynthesizer.write
1731
- Task {
1732
- do {
1733
- var audioBuffers: [AVAudioPCMBuffer] = []
1734
-
1735
- synthesizer.write(utterance) { buffer in
1736
- if let buffer = buffer as? AVAudioPCMBuffer {
1737
- audioBuffers.append(buffer)
1738
- } else if buffer == nil {
1739
- // Writing is complete when buffer is nil
1740
- Task {
1741
- do {
1742
- // Write all buffers to file
1743
- try self.writeBuffersToFile(buffers: audioBuffers, url: outputURL)
1744
- continuation.resume(returning: outputURL.path)
1745
- } catch {
1746
- continuation.resume(throwing: error)
1747
- }
1748
- }
1749
- }
1750
- }
1770
+ do {
1771
+ // Create audio file on first buffer
1772
+ if audioFile == nil {
1773
+ print("📝 Creating audio file with format: \(pcmBuffer.format)")
1774
+ audioFile = try AVAudioFile(
1775
+ forWriting: outputURL,
1776
+ settings: pcmBuffer.format.settings
1777
+ )
1778
+ }
1779
+
1780
+ // Write buffer to file
1781
+ try audioFile?.write(from: pcmBuffer)
1782
+
1783
+ if bufferCount % 10 == 0 {
1784
+ print("📦 Written \(bufferCount) buffers...")
1751
1785
  }
1786
+ } catch {
1787
+ print("❌ Error writing buffer: \(error)")
1788
+ capturedError = error
1789
+ semaphore.signal()
1752
1790
  }
1753
1791
  }
1792
+
1793
+ // Wait for completion
1794
+ print("⏳ Waiting for audio generation to complete...")
1795
+ semaphore.wait()
1796
+
1797
+ // Check for errors
1798
+ if let error = capturedError {
1799
+ throw error
1800
+ }
1801
+
1802
+ // Verify audio file was created
1803
+ guard audioFile != nil else {
1804
+ throw NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "No audio data was generated"])
1805
+ }
1806
+
1807
+ print("✅ Audio file created: \(outputURL.path)")
1808
+ return outputURL.path
1754
1809
  }
1755
1810
 
1756
1811
  private func writeBuffersToFile(buffers: [AVAudioPCMBuffer], url: URL) throws {
1812
+ guard !buffers.isEmpty else {
1813
+ throw NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "No audio data generated"])
1814
+ }
1815
+
1757
1816
  guard let firstBuffer = buffers.first else {
1758
1817
  throw NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "No audio data generated"])
1759
1818
  }
1760
1819
 
1820
+ // Verify that the buffer has valid data
1821
+ guard firstBuffer.frameLength > 0 else {
1822
+ throw NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "Audio buffer is empty (frameLength is 0)"])
1823
+ }
1824
+
1761
1825
  // Create audio file with settings from the buffer
1762
1826
  let settings: [String: Any] = [
1763
1827
  AVFormatIDKey: kAudioFormatLinearPCM,
@@ -1772,7 +1836,10 @@ public class ReactNativeKookitModule: Module {
1772
1836
 
1773
1837
  // Write all buffers to file
1774
1838
  for buffer in buffers {
1775
- try audioFile.write(from: buffer)
1839
+ // Skip empty buffers
1840
+ if buffer.frameLength > 0 {
1841
+ try audioFile.write(from: buffer)
1842
+ }
1776
1843
  }
1777
1844
  }
1778
1845
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-kookit",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
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",