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 +31 -0
- package/android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt +7 -0
- package/build/ReactNativeKookitModule.d.ts +8 -0
- package/build/ReactNativeKookitModule.d.ts.map +1 -1
- package/build/ReactNativeKookitModule.js.map +1 -1
- package/ios/ReactNativeKookitModule.swift +140 -73
- package/package.json +1 -1
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":"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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;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
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
-
|
|
1578
|
+
let synth = AVSpeechSynthesizer()
|
|
1579
|
+
synth.usesApplicationAudioSession = true
|
|
1580
|
+
self.speechSynthesizer = synth
|
|
1570
1581
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
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
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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
|
-
//
|
|
1712
|
-
|
|
1713
|
-
|
|
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
|
-
//
|
|
1727
|
-
|
|
1728
|
-
let outputURL = ttsDir.appendingPathComponent(fileName)
|
|
1767
|
+
// Process non-empty buffer
|
|
1768
|
+
bufferCount += 1
|
|
1729
1769
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
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
|
-
|
|
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