react-native-sherpa-onnx 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +90 -21
  3. package/SherpaOnnx.podspec +3 -0
  4. package/THIRD_PARTY_LICENSES/README.md +62 -0
  5. package/THIRD_PARTY_LICENSES/ffmpeg.txt +502 -0
  6. package/THIRD_PARTY_LICENSES/libarchive.txt +65 -0
  7. package/THIRD_PARTY_LICENSES/nvidia_omla.txt +181 -0
  8. package/THIRD_PARTY_LICENSES/onnxruntime.txt +21 -0
  9. package/THIRD_PARTY_LICENSES/opus.txt +44 -0
  10. package/THIRD_PARTY_LICENSES/sherpa-onnx.txt +201 -0
  11. package/THIRD_PARTY_LICENSES/shine.txt +482 -0
  12. package/THIRD_PARTY_LICENSES/zstd.txt +30 -0
  13. package/android/build.gradle +7 -3
  14. package/android/prebuilt-download.gradle +345 -153
  15. package/android/prebuilt-versions.gradle +2 -2
  16. package/android/src/main/assets/model_licenses/asr-models-license-status.csv +409 -0
  17. package/android/src/main/assets/model_licenses/qnn-asr-models-license-status.csv +695 -0
  18. package/android/src/main/assets/model_licenses/tts-models-license-status.csv +596 -0
  19. package/android/src/main/cpp/CMakeLists.txt +28 -10
  20. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +306 -6
  21. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +33 -4
  22. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +266 -7
  23. package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +268 -2
  24. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +6 -2
  25. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-tts.cpp +4 -2
  26. package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +137 -7
  27. package/android/src/main/java/com/sherpaonnx/SherpaOnnxAssetHelper.kt +51 -6
  28. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +159 -0
  29. package/android/src/main/java/com/sherpaonnx/SherpaOnnxOnlineSttHelper.kt +4 -1
  30. package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +112 -97
  31. package/ios/Resources/model_licenses/asr-models-license-status.csv +409 -0
  32. package/ios/Resources/model_licenses/qnn-asr-models-license-status.csv +695 -0
  33. package/ios/Resources/model_licenses/tts-models-license-status.csv +596 -0
  34. package/ios/SherpaOnnx+OnlineSTT.mm +2 -0
  35. package/ios/SherpaOnnx+PcmLiveStream.mm +2 -29
  36. package/ios/SherpaOnnx+TTS.mm +178 -20
  37. package/ios/SherpaOnnx.mm +108 -1
  38. package/ios/SherpaOnnxAudioConvert.h +10 -0
  39. package/ios/SherpaOnnxAudioConvert.mm +257 -1
  40. package/ios/archive/sherpa-onnx-archive-helper.h +10 -0
  41. package/ios/archive/sherpa-onnx-archive-helper.mm +56 -5
  42. package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +13 -2
  43. package/ios/model_detect/sherpa-onnx-validate-tts.mm +4 -2
  44. package/ios/online_stt/sherpa-onnx-online-stt-wrapper.h +1 -0
  45. package/ios/online_stt/sherpa-onnx-online-stt-wrapper.mm +4 -0
  46. package/ios/tts/sherpa-onnx-tts-wrapper.h +37 -0
  47. package/ios/tts/sherpa-onnx-tts-wrapper.mm +149 -3
  48. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  49. package/lib/module/audio/index.js +8 -0
  50. package/lib/module/audio/index.js.map +1 -1
  51. package/lib/module/download/ModelDownloadManager.js +10 -929
  52. package/lib/module/download/ModelDownloadManager.js.map +1 -1
  53. package/lib/module/download/activeModelOperations.js +26 -0
  54. package/lib/module/download/activeModelOperations.js.map +1 -0
  55. package/lib/module/download/background-downloader.d.js +2 -0
  56. package/lib/module/download/background-downloader.d.js.map +1 -0
  57. package/lib/module/download/bulkPurge.js +72 -0
  58. package/lib/module/download/bulkPurge.js.map +1 -0
  59. package/lib/module/download/checksumPrompt.js +19 -0
  60. package/lib/module/download/checksumPrompt.js.map +1 -0
  61. package/lib/module/download/constants.js +7 -0
  62. package/lib/module/download/constants.js.map +1 -0
  63. package/lib/module/download/downloadEvents.js +35 -0
  64. package/lib/module/download/downloadEvents.js.map +1 -0
  65. package/lib/module/download/downloadTask.js +385 -0
  66. package/lib/module/download/downloadTask.js.map +1 -0
  67. package/lib/module/download/ensureModel.js +89 -0
  68. package/lib/module/download/ensureModel.js.map +1 -0
  69. package/lib/module/download/index.js +4 -3
  70. package/lib/module/download/index.js.map +1 -1
  71. package/lib/module/download/localModels.js +151 -0
  72. package/lib/module/download/localModels.js.map +1 -0
  73. package/lib/module/download/modelExtraction.js +174 -0
  74. package/lib/module/download/modelExtraction.js.map +1 -0
  75. package/lib/module/download/paths.js +98 -0
  76. package/lib/module/download/paths.js.map +1 -0
  77. package/lib/module/download/postDownloadProcessing.js +206 -0
  78. package/lib/module/download/postDownloadProcessing.js.map +1 -0
  79. package/lib/module/download/protectedModelKeys.js +31 -0
  80. package/lib/module/download/protectedModelKeys.js.map +1 -0
  81. package/lib/module/download/registry.js +267 -0
  82. package/lib/module/download/registry.js.map +1 -0
  83. package/lib/module/download/retry.js +59 -0
  84. package/lib/module/download/retry.js.map +1 -0
  85. package/lib/module/download/types.js +17 -0
  86. package/lib/module/download/types.js.map +1 -0
  87. package/lib/module/download/validation.js +101 -5
  88. package/lib/module/download/validation.js.map +1 -1
  89. package/lib/module/{download → extraction}/extractTarBz2.js +3 -1
  90. package/lib/module/extraction/extractTarBz2.js.map +1 -0
  91. package/lib/module/extraction/extractTarZst.js +54 -0
  92. package/lib/module/extraction/extractTarZst.js.map +1 -0
  93. package/lib/module/extraction/index.js +190 -0
  94. package/lib/module/extraction/index.js.map +1 -0
  95. package/lib/module/extraction/types.js +2 -0
  96. package/lib/module/extraction/types.js.map +1 -0
  97. package/lib/module/index.js +2 -1
  98. package/lib/module/index.js.map +1 -1
  99. package/lib/module/licenses.js +63 -0
  100. package/lib/module/licenses.js.map +1 -0
  101. package/lib/module/stt/index.js +16 -2
  102. package/lib/module/stt/index.js.map +1 -1
  103. package/lib/module/stt/streaming.js +2 -0
  104. package/lib/module/stt/streaming.js.map +1 -1
  105. package/lib/module/stt/streamingTypes.js.map +1 -1
  106. package/lib/module/stt/types.js.map +1 -1
  107. package/lib/module/tts/index.js +20 -2
  108. package/lib/module/tts/index.js.map +1 -1
  109. package/lib/module/tts/streaming.js +4 -0
  110. package/lib/module/tts/streaming.js.map +1 -1
  111. package/lib/module/tts/types.js.map +1 -1
  112. package/lib/module/utils.js +16 -1
  113. package/lib/module/utils.js.map +1 -1
  114. package/lib/typescript/src/NativeSherpaOnnx.d.ts +72 -5
  115. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  116. package/lib/typescript/src/audio/index.d.ts +10 -0
  117. package/lib/typescript/src/audio/index.d.ts.map +1 -1
  118. package/lib/typescript/src/download/ModelDownloadManager.d.ts +10 -108
  119. package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -1
  120. package/lib/typescript/src/download/activeModelOperations.d.ts +6 -0
  121. package/lib/typescript/src/download/activeModelOperations.d.ts.map +1 -0
  122. package/lib/typescript/src/download/bulkPurge.d.ts +14 -0
  123. package/lib/typescript/src/download/bulkPurge.d.ts.map +1 -0
  124. package/lib/typescript/src/download/checksumPrompt.d.ts +3 -0
  125. package/lib/typescript/src/download/checksumPrompt.d.ts.map +1 -0
  126. package/lib/typescript/src/download/constants.d.ts +5 -0
  127. package/lib/typescript/src/download/constants.d.ts.map +1 -0
  128. package/lib/typescript/src/download/downloadEvents.d.ts +6 -0
  129. package/lib/typescript/src/download/downloadEvents.d.ts.map +1 -0
  130. package/lib/typescript/src/download/downloadTask.d.ts +20 -0
  131. package/lib/typescript/src/download/downloadTask.d.ts.map +1 -0
  132. package/lib/typescript/src/download/ensureModel.d.ts +26 -0
  133. package/lib/typescript/src/download/ensureModel.d.ts.map +1 -0
  134. package/lib/typescript/src/download/index.d.ts +7 -5
  135. package/lib/typescript/src/download/index.d.ts.map +1 -1
  136. package/lib/typescript/src/download/localModels.d.ts +15 -0
  137. package/lib/typescript/src/download/localModels.d.ts.map +1 -0
  138. package/lib/typescript/src/download/modelExtraction.d.ts +36 -0
  139. package/lib/typescript/src/download/modelExtraction.d.ts.map +1 -0
  140. package/lib/typescript/src/download/paths.d.ts +28 -0
  141. package/lib/typescript/src/download/paths.d.ts.map +1 -0
  142. package/lib/typescript/src/download/postDownloadProcessing.d.ts +19 -0
  143. package/lib/typescript/src/download/postDownloadProcessing.d.ts.map +1 -0
  144. package/lib/typescript/src/download/protectedModelKeys.d.ts +6 -0
  145. package/lib/typescript/src/download/protectedModelKeys.d.ts.map +1 -0
  146. package/lib/typescript/src/download/registry.d.ts +14 -0
  147. package/lib/typescript/src/download/registry.d.ts.map +1 -0
  148. package/lib/typescript/src/download/retry.d.ts +15 -0
  149. package/lib/typescript/src/download/retry.d.ts.map +1 -0
  150. package/lib/typescript/src/download/types.d.ts +96 -0
  151. package/lib/typescript/src/download/types.d.ts.map +1 -0
  152. package/lib/typescript/src/download/validation.d.ts +19 -0
  153. package/lib/typescript/src/download/validation.d.ts.map +1 -1
  154. package/lib/typescript/src/extraction/extractTarBz2.d.ts.map +1 -0
  155. package/lib/typescript/src/extraction/extractTarZst.d.ts +14 -0
  156. package/lib/typescript/src/extraction/extractTarZst.d.ts.map +1 -0
  157. package/lib/typescript/src/extraction/index.d.ts +50 -0
  158. package/lib/typescript/src/extraction/index.d.ts.map +1 -0
  159. package/lib/typescript/src/extraction/types.d.ts +60 -0
  160. package/lib/typescript/src/extraction/types.d.ts.map +1 -0
  161. package/lib/typescript/src/index.d.ts +1 -0
  162. package/lib/typescript/src/index.d.ts.map +1 -1
  163. package/lib/typescript/src/licenses.d.ts +10 -0
  164. package/lib/typescript/src/licenses.d.ts.map +1 -0
  165. package/lib/typescript/src/stt/index.d.ts +4 -1
  166. package/lib/typescript/src/stt/index.d.ts.map +1 -1
  167. package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
  168. package/lib/typescript/src/stt/streamingTypes.d.ts +5 -0
  169. package/lib/typescript/src/stt/streamingTypes.d.ts.map +1 -1
  170. package/lib/typescript/src/stt/types.d.ts +3 -1
  171. package/lib/typescript/src/stt/types.d.ts.map +1 -1
  172. package/lib/typescript/src/tts/index.d.ts +3 -1
  173. package/lib/typescript/src/tts/index.d.ts.map +1 -1
  174. package/lib/typescript/src/tts/streaming.d.ts.map +1 -1
  175. package/lib/typescript/src/tts/types.d.ts +6 -5
  176. package/lib/typescript/src/tts/types.d.ts.map +1 -1
  177. package/lib/typescript/src/utils.d.ts +5 -0
  178. package/lib/typescript/src/utils.d.ts.map +1 -1
  179. package/package.json +11 -1
  180. package/scripts/{check-model-csvs.sh → ci/check-model-csvs.sh} +9 -2
  181. package/scripts/ci/collect_all_sherpa_model_streams.sh +101 -0
  182. package/scripts/ci/collect_one_sherpa_release_stream.sh +189 -0
  183. package/scripts/ci/sherpa_asr_model_release_streams.json +21 -0
  184. package/scripts/ci/sherpa_tts_model_release_streams.json +13 -0
  185. package/scripts/ci/update_model_license_csv.sh +765 -0
  186. package/scripts/setup-ios-framework.sh +14 -11
  187. package/scripts/update_commercial_use.js +73 -0
  188. package/src/NativeSherpaOnnx.ts +92 -5
  189. package/src/audio/index.ts +20 -0
  190. package/src/download/ModelDownloadManager.ts +55 -1343
  191. package/src/download/activeModelOperations.ts +38 -0
  192. package/src/download/background-downloader.d.ts +43 -0
  193. package/src/download/bulkPurge.ts +102 -0
  194. package/src/download/checksumPrompt.ts +25 -0
  195. package/src/download/constants.ts +5 -0
  196. package/src/download/downloadEvents.ts +55 -0
  197. package/src/download/downloadTask.ts +497 -0
  198. package/src/download/ensureModel.ts +124 -0
  199. package/src/download/index.ts +19 -2
  200. package/src/download/localModels.ts +234 -0
  201. package/src/download/modelExtraction.ts +244 -0
  202. package/src/download/paths.ts +134 -0
  203. package/src/download/postDownloadProcessing.ts +292 -0
  204. package/src/download/protectedModelKeys.ts +30 -0
  205. package/src/download/registry.ts +404 -0
  206. package/src/download/retry.ts +76 -0
  207. package/src/download/types.ts +120 -0
  208. package/src/download/validation.ts +114 -8
  209. package/src/{download → extraction}/extractTarBz2.ts +3 -1
  210. package/src/extraction/extractTarZst.ts +79 -0
  211. package/src/extraction/index.ts +269 -0
  212. package/src/extraction/types.ts +63 -0
  213. package/src/index.tsx +2 -0
  214. package/src/licenses.ts +100 -0
  215. package/src/stt/index.ts +20 -2
  216. package/src/stt/streaming.ts +3 -0
  217. package/src/stt/streamingTypes.ts +5 -0
  218. package/src/stt/types.ts +3 -1
  219. package/src/tts/index.ts +30 -2
  220. package/src/tts/streaming.ts +10 -0
  221. package/src/tts/types.ts +6 -5
  222. package/src/utils.ts +22 -1
  223. package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -1
  224. package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -1
  225. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
  226. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
  227. package/android/src/main/cpp/jni/tts/sherpa-onnx-tts-zipvoice-jni.cpp +0 -301
  228. package/android/src/main/java/com/sherpaonnx/ZipvoiceTtsWrapper.kt +0 -187
  229. package/lib/module/download/extractTarBz2.js.map +0 -1
  230. package/lib/typescript/src/download/extractTarBz2.d.ts.map +0 -1
  231. package/scripts/check-qnn-support.sh +0 -78
  232. /package/lib/typescript/src/{download → extraction}/extractTarBz2.d.ts +0 -0
@@ -1,7 +1,10 @@
1
1
  package com.sherpaonnx
2
2
 
3
+ import android.content.Context
4
+ import android.util.Log
3
5
  import com.facebook.react.bridge.Arguments
4
6
  import com.facebook.react.bridge.Promise
7
+ import java.util.concurrent.ConcurrentHashMap
5
8
  import java.util.concurrent.ExecutorService
6
9
  import java.util.concurrent.Executors
7
10
  import java.util.concurrent.atomic.AtomicBoolean
@@ -11,11 +14,12 @@ import java.util.concurrent.atomic.AtomicBoolean
11
14
  * This class delegates to C++ native implementation via JNI.
12
15
  */
13
16
  class SherpaOnnxArchiveHelper {
14
- private val cancelRequested = AtomicBoolean(false)
15
-
16
17
  companion object {
17
- /** Single-thread executor so extractions run off the React Native bridge thread and do not block listDownloadedModelsByCategory / RNFS. */
18
- private val extractExecutor: ExecutorService = Executors.newSingleThreadExecutor()
18
+ /** Thread pool for extractions allows up to 2 concurrent extractions while keeping them off the React Native bridge thread. */
19
+ private val extractExecutor: ExecutorService = Executors.newFixedThreadPool(2)
20
+
21
+ /** Per-source-path cancellation flags. Key = absolute source archive path. */
22
+ private val cancelFlags = ConcurrentHashMap<String, AtomicBoolean>()
19
23
 
20
24
  init {
21
25
  try {
@@ -27,10 +31,24 @@ class SherpaOnnxArchiveHelper {
27
31
  }
28
32
 
29
33
  fun cancelExtractTarBz2() {
30
- cancelRequested.set(true)
34
+ // Cancel ALL ongoing extractions (legacy global cancel)
35
+ for (flag in cancelFlags.values) flag.set(true)
36
+ nativeCancelExtract()
37
+ }
38
+
39
+ fun cancelExtractTarZst() {
40
+ // Cancel ALL ongoing extractions (legacy global cancel)
41
+ for (flag in cancelFlags.values) flag.set(true)
31
42
  nativeCancelExtract()
32
43
  }
33
44
 
45
+ /** Cancel a specific extraction identified by its source archive path. */
46
+ fun cancelExtractBySourcePath(sourcePath: String) {
47
+ // Only set the per-path flag; do not call nativeCancelExtract() since that is
48
+ // a global cancel that would also interrupt unrelated concurrent extractions.
49
+ cancelFlags[sourcePath]?.set(true)
50
+ }
51
+
34
52
  fun extractTarBz2(
35
53
  sourcePath: String,
36
54
  targetPath: String,
@@ -48,7 +66,9 @@ class SherpaOnnxArchiveHelper {
48
66
  }
49
67
 
50
68
  try {
51
- cancelRequested.set(false)
69
+ // Register per-path cancel flag
70
+ val cancelFlag = AtomicBoolean(false)
71
+ cancelFlags[sourcePath] = cancelFlag
52
72
 
53
73
  // Create a progress callback object that JNI can call
54
74
  val progressCallback = object : Any() {
@@ -58,19 +78,113 @@ class SherpaOnnxArchiveHelper {
58
78
  }
59
79
 
60
80
  // Run extraction on a background thread so the React Native bridge thread is not blocked.
61
- // Otherwise listDownloadedModelsByCategory (RNFS) and other native calls would wait until extraction finishes.
81
+ // The thread pool allows multiple extractions in parallel.
62
82
  extractExecutor.execute {
63
83
  try {
84
+ // Check per-path cancel flag before starting the native extraction.
85
+ if (cancelFlag.get()) {
86
+ resolveOnce(false, "Cancelled")
87
+ return@execute
88
+ }
64
89
  nativeExtractTarBz2(sourcePath, targetPath, force, progressCallback, promise)
65
90
  } catch (e: Exception) {
66
91
  resolveOnce(false, "Archive extraction error: ${e.message}")
92
+ } finally {
93
+ cancelFlags.remove(sourcePath)
94
+ }
95
+ }
96
+ } catch (e: Exception) {
97
+ cancelFlags.remove(sourcePath)
98
+ resolveOnce(false, "Archive extraction error: ${e.message}")
99
+ }
100
+ }
101
+
102
+ fun extractTarZst(
103
+ sourcePath: String,
104
+ targetPath: String,
105
+ force: Boolean,
106
+ promise: Promise,
107
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
108
+ ) {
109
+ val promiseSettled = AtomicBoolean(false)
110
+ fun resolveOnce(success: Boolean, reason: String? = null) {
111
+ if (!promiseSettled.compareAndSet(false, true)) return
112
+ val result = Arguments.createMap()
113
+ result.putBoolean("success", success)
114
+ if (reason != null) result.putString("reason", reason)
115
+ promise.resolve(result)
116
+ }
117
+
118
+ try {
119
+ val cancelFlag = AtomicBoolean(false)
120
+ cancelFlags[sourcePath] = cancelFlag
121
+
122
+ val progressCallback = object : Any() {
123
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
124
+ onProgress(bytesExtracted, totalBytes, percent)
125
+ }
126
+ }
127
+ extractExecutor.execute {
128
+ try {
129
+ // Check per-path cancel flag before starting the native extraction.
130
+ if (cancelFlag.get()) {
131
+ resolveOnce(false, "Cancelled")
132
+ return@execute
133
+ }
134
+ nativeExtractTarZst(sourcePath, targetPath, force, progressCallback, promise)
135
+ } catch (e: Exception) {
136
+ resolveOnce(false, "Archive extraction error: ${e.message}")
137
+ } finally {
138
+ cancelFlags.remove(sourcePath)
67
139
  }
68
140
  }
69
141
  } catch (e: Exception) {
142
+ cancelFlags.remove(sourcePath)
70
143
  resolveOnce(false, "Archive extraction error: ${e.message}")
71
144
  }
72
145
  }
73
146
 
147
+ fun extractTarZstFromAsset(
148
+ context: Context,
149
+ assetPath: String,
150
+ targetPath: String,
151
+ force: Boolean,
152
+ promise: Promise,
153
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
154
+ ) {
155
+ if (BuildConfig.DEBUG) {
156
+ Log.i("SherpaOnnx", "extractTarZstFromAsset assetPath=$assetPath targetPath=$targetPath")
157
+ }
158
+ val progressCallback = object : Any() {
159
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
160
+ onProgress(bytesExtracted, totalBytes, percent)
161
+ }
162
+ }
163
+ extractExecutor.execute {
164
+ try {
165
+ context.assets.open(assetPath).use { stream ->
166
+ nativeExtractTarZstFromStream(stream, targetPath, force, progressCallback, promise)
167
+ }
168
+ } catch (e: Exception) {
169
+ val result = Arguments.createMap()
170
+ result.putBoolean("success", false)
171
+ result.putString("reason", e.message ?: "Failed to open asset")
172
+ promise.resolve(result)
173
+ }
174
+ }
175
+ }
176
+
177
+ fun extractTarBz2FromAsset(
178
+ context: Context,
179
+ assetPath: String,
180
+ targetPath: String,
181
+ force: Boolean,
182
+ promise: Promise,
183
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
184
+ ) {
185
+ extractTarZstFromAsset(context, assetPath, targetPath, force, promise, onProgress)
186
+ }
187
+
74
188
  fun computeFileSha256(filePath: String, promise: Promise) {
75
189
  nativeComputeFileSha256(filePath, promise)
76
190
  }
@@ -84,6 +198,22 @@ class SherpaOnnxArchiveHelper {
84
198
  promise: Promise
85
199
  )
86
200
 
201
+ private external fun nativeExtractTarZst(
202
+ sourcePath: String,
203
+ targetPath: String,
204
+ force: Boolean,
205
+ progressCallback: Any?,
206
+ promise: Promise
207
+ )
208
+
209
+ private external fun nativeExtractTarZstFromStream(
210
+ inputStream: java.io.InputStream,
211
+ targetPath: String,
212
+ force: Boolean,
213
+ progressCallback: Any?,
214
+ promise: Promise
215
+ )
216
+
87
217
  private external fun nativeCancelExtract()
88
218
 
89
219
  private external fun nativeComputeFileSha256(
@@ -82,10 +82,12 @@ internal class SherpaOnnxAssetHelper(
82
82
  try {
83
83
  val baseDir = File(path)
84
84
  if (!baseDir.exists()) {
85
- throw IllegalArgumentException("Path does not exist: $path")
85
+ promise.resolve(Arguments.createArray())
86
+ return
86
87
  }
87
88
  if (!baseDir.isDirectory) {
88
- throw IllegalArgumentException("Path is not a directory: $path")
89
+ promise.resolve(Arguments.createArray())
90
+ return
89
91
  }
90
92
 
91
93
  val folders = mutableListOf<String>()
@@ -134,11 +136,18 @@ internal class SherpaOnnxAssetHelper(
134
136
  try {
135
137
  Log.i(logTag, "getAssetPackPath: packName=$packName")
136
138
  val assetPackManager = AssetPackManagerFactory.getInstance(context)
137
- val location: AssetPackLocation? = assetPackManager.getPackLocation(packName)
139
+ var location: AssetPackLocation? = assetPackManager.getPackLocation(packName)
138
140
  if (location == null) {
139
- Log.i(logTag, "getAssetPackPath: location is null for pack '$packName'")
140
- promise.resolve(null)
141
- return
141
+ val allLocations = assetPackManager.getPackLocations()
142
+ location = allLocations?.get(packName)
143
+ if (allLocations != null) {
144
+ Log.i(logTag, "getAssetPackPath: getPackLocation was null, getPackLocations keys=${allLocations.keys}")
145
+ }
146
+ if (location == null) {
147
+ Log.i(logTag, "getAssetPackPath: location is null for pack '$packName'")
148
+ promise.resolve(null)
149
+ return
150
+ }
142
151
  }
143
152
  Log.i(logTag, "getAssetPackPath: storageMethod=${location.packStorageMethod()}, " +
144
153
  "assetsPath=${location.assetsPath()}, path=${location.path()}")
@@ -170,6 +179,42 @@ internal class SherpaOnnxAssetHelper(
170
179
  }
171
180
  }
172
181
 
182
+ /**
183
+ * Lists asset paths of .tar.zst and .tar.bz2 archives in a PAD pack when stored as APK_ASSETS.
184
+ * Returns empty array when pack is null or STORAGE_FILES (caller uses path + readDir in that case).
185
+ * APK_ASSETS: pack content is merged into the app asset root; canonical path is "models"
186
+ * (pack layout src/main/assets/models/). Same for Play Store and bundletool install-time delivery.
187
+ */
188
+ fun listBundledArchiveAssetPaths(packName: String, promise: Promise) {
189
+ try {
190
+ val assetPackManager = AssetPackManagerFactory.getInstance(context)
191
+ var location: AssetPackLocation? = assetPackManager.getPackLocation(packName)
192
+ if (location == null) {
193
+ location = assetPackManager.getPackLocations()?.get(packName)
194
+ }
195
+ if (location == null) {
196
+ promise.resolve(Arguments.createArray())
197
+ return
198
+ }
199
+ if (location.packStorageMethod() != AssetPackStorageMethod.STORAGE_FILES) {
200
+ val assetPrefix = "models"
201
+ val names = context.assets.list(assetPrefix) ?: emptyArray()
202
+ val archives = names.filter { it.endsWith(".tar.zst") || it.endsWith(".tar.bz2") }
203
+ val result = Arguments.createArray()
204
+ for (name in archives) {
205
+ result.pushString("$assetPrefix/$name")
206
+ }
207
+ Log.i(logTag, "listBundledArchiveAssetPaths: packName=$packName prefix=$assetPrefix count=${result.size()}")
208
+ promise.resolve(result)
209
+ } else {
210
+ promise.resolve(Arguments.createArray())
211
+ }
212
+ } catch (e: Exception) {
213
+ Log.w(logTag, "listBundledArchiveAssetPaths failed: ${e.message}")
214
+ promise.resolve(Arguments.createArray())
215
+ }
216
+ }
217
+
173
218
  private fun resolveAssetPath(assetPath: String): String {
174
219
  Log.i(logTag, "resolveAssetPath: assetPath=$assetPath")
175
220
  val assetManager = context.assets
@@ -8,6 +8,7 @@ import com.facebook.react.bridge.ReadableMap
8
8
  import com.facebook.react.bridge.Arguments
9
9
  import com.facebook.react.module.annotations.ReactModule
10
10
  import com.facebook.react.modules.core.DeviceEventManagerModule
11
+ import com.k2fsa.sherpa.onnx.WaveReader
11
12
 
12
13
  @ReactModule(name = SherpaOnnxModule.NAME)
13
14
  class SherpaOnnxModule(reactContext: ReactApplicationContext) :
@@ -303,6 +304,22 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
303
304
  promise.resolve(null)
304
305
  }
305
306
 
307
+ override fun extractTarZst(sourcePath: String, targetPath: String, force: Boolean, promise: Promise) {
308
+ archiveHelper.extractTarZst(sourcePath, targetPath, force, promise) { bytes, total, percent ->
309
+ emitExtractTarZstProgress(sourcePath, bytes, total, percent)
310
+ }
311
+ }
312
+
313
+ override fun cancelExtractTarZst(promise: Promise) {
314
+ archiveHelper.cancelExtractTarZst()
315
+ promise.resolve(null)
316
+ }
317
+
318
+ override fun cancelExtractBySourcePath(sourcePath: String, promise: Promise) {
319
+ archiveHelper.cancelExtractBySourcePath(sourcePath)
320
+ promise.resolve(null)
321
+ }
322
+
306
323
  override fun computeFileSha256(filePath: String, promise: Promise) {
307
324
  archiveHelper.computeFileSha256(filePath, promise)
308
325
  }
@@ -318,6 +335,17 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
318
335
  eventEmitter.emit("extractTarBz2Progress", payload)
319
336
  }
320
337
 
338
+ private fun emitExtractTarZstProgress(sourcePath: String, bytes: Long, totalBytes: Long, percent: Double) {
339
+ val eventEmitter = reactApplicationContext
340
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
341
+ val payload = Arguments.createMap()
342
+ payload.putString("sourcePath", sourcePath)
343
+ payload.putDouble("bytes", bytes.toDouble())
344
+ payload.putDouble("totalBytes", totalBytes.toDouble())
345
+ payload.putDouble("percent", percent)
346
+ eventEmitter.emit("extractTarZstProgress", payload)
347
+ }
348
+
321
349
  /**
322
350
  * Resolve asset path - copy from assets to internal storage if needed
323
351
  * Preserves the directory structure from assets (e.g., test_wavs/ stays as test_wavs/)
@@ -430,6 +458,7 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
430
458
  val provider = if (options.hasKey("provider")) options.getString("provider") else null
431
459
  val ruleFsts = if (options.hasKey("ruleFsts")) options.getString("ruleFsts") else null
432
460
  val ruleFars = if (options.hasKey("ruleFars")) options.getString("ruleFars") else null
461
+ val dither = if (options.hasKey("dither")) options.getDouble("dither") else null
433
462
  val blankPenalty = if (options.hasKey("blankPenalty")) options.getDouble("blankPenalty") else null
434
463
  val debug = if (options.hasKey("debug")) options.getBoolean("debug") else null
435
464
  val rule1MustContainNonSilence = if (options.hasKey("rule1MustContainNonSilence")) options.getBoolean("rule1MustContainNonSilence") else null
@@ -454,6 +483,7 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
454
483
  provider,
455
484
  ruleFsts,
456
485
  ruleFars,
486
+ dither,
457
487
  blankPenalty,
458
488
  debug,
459
489
  rule1MustContainNonSilence,
@@ -690,6 +720,72 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
690
720
  }
691
721
  }
692
722
 
723
+ /**
724
+ * Decode audio to mono float samples (approx. [-1, 1]) and effective sample rate.
725
+ * Same path/URI handling as [convertAudioToFormat]. WAV may use [WaveReader] when no resample is requested.
726
+ */
727
+ override fun decodeAudioFileToFloatSamples(inputPath: String, targetSampleRateHz: Double?, promise: Promise) {
728
+ var tmpFile: java.io.File? = null
729
+ try {
730
+ val targetHz = (targetSampleRateHz ?: 0.0).toInt()
731
+ if (targetHz < 0) {
732
+ promise.reject("DECODE_ERROR", "targetSampleRateHz must be >= 0")
733
+ return
734
+ }
735
+ val (pathToUse, tmp) = resolveInputForConvert(inputPath)
736
+ tmpFile = tmp
737
+
738
+ if (pathToUse.endsWith(".wav", ignoreCase = true)) {
739
+ try {
740
+ val wave = WaveReader.readWave(pathToUse)
741
+ val s = wave.samples
742
+ if (s != null && s.isNotEmpty() && wave.sampleRate > 0 && (targetHz == 0 || targetHz == wave.sampleRate)) {
743
+ val map = Arguments.createMap()
744
+ val arr = Arguments.createArray()
745
+ for (i in s.indices) {
746
+ arr.pushDouble(s[i].toDouble())
747
+ }
748
+ map.putArray("samples", arr)
749
+ map.putInt("sampleRate", wave.sampleRate)
750
+ promise.resolve(map)
751
+ return
752
+ }
753
+ } catch (_: Throwable) {
754
+ // Fall through to FFmpeg/native path (e.g. odd WAV or resample requested).
755
+ }
756
+ }
757
+
758
+ val result = Companion.nativeDecodeAudioFileToFloatSamples(pathToUse, targetHz)
759
+ if (result.size == 1 && result[0] is String) {
760
+ promise.reject("DECODE_ERROR", result[0] as String)
761
+ return
762
+ }
763
+ if (result.size != 2 || result[0] !is FloatArray) {
764
+ promise.reject("DECODE_ERROR", "Unexpected native decode result")
765
+ return
766
+ }
767
+ val floats = result[0] as FloatArray
768
+ val rateObj = result.getOrNull(1) as? Number ?: run {
769
+ promise.reject("DECODE_ERROR", "Unexpected sample rate in native decode result")
770
+ return
771
+ }
772
+ val sr = rateObj.toInt()
773
+ val map = Arguments.createMap()
774
+ val arr = Arguments.createArray()
775
+ for (i in floats.indices) {
776
+ arr.pushDouble(floats[i].toDouble())
777
+ }
778
+ map.putArray("samples", arr)
779
+ map.putInt("sampleRate", sr)
780
+ promise.resolve(map)
781
+ } catch (e: Exception) {
782
+ android.util.Log.e(NAME, "DECODE_EXCEPTION: ${e.message}", e)
783
+ promise.reject("DECODE_EXCEPTION", e.message ?: "Failed to decode audio", e)
784
+ } finally {
785
+ tmpFile?.delete()
786
+ }
787
+ }
788
+
693
789
  // ==================== TTS Methods ====================
694
790
 
695
791
  /**
@@ -997,6 +1093,63 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
997
1093
  assetHelper.getAssetPackPath(packName, promise)
998
1094
  }
999
1095
 
1096
+ override fun listBundledArchiveAssetPaths(packName: String, promise: Promise) {
1097
+ assetHelper.listBundledArchiveAssetPaths(packName, promise)
1098
+ }
1099
+
1100
+ override fun extractTarZstFromAsset(
1101
+ assetPath: String,
1102
+ targetPath: String,
1103
+ force: Boolean,
1104
+ promise: Promise
1105
+ ) {
1106
+ archiveHelper.extractTarZstFromAsset(
1107
+ reactApplicationContext,
1108
+ assetPath,
1109
+ targetPath,
1110
+ force,
1111
+ promise
1112
+ ) { bytes, total, percent ->
1113
+ emitExtractTarZstProgress(assetPath, bytes, total, percent)
1114
+ }
1115
+ }
1116
+
1117
+ override fun extractTarBz2FromAsset(
1118
+ assetPath: String,
1119
+ targetPath: String,
1120
+ force: Boolean,
1121
+ promise: Promise
1122
+ ) {
1123
+ archiveHelper.extractTarBz2FromAsset(
1124
+ reactApplicationContext,
1125
+ assetPath,
1126
+ targetPath,
1127
+ force,
1128
+ promise
1129
+ ) { bytes, total, percent ->
1130
+ emitExtractProgress(assetPath, bytes, total, percent)
1131
+ }
1132
+ }
1133
+
1134
+ override fun readAssetFileAsUtf8(assetPath: String, promise: Promise) {
1135
+ // Validate assetPath to prevent path traversal: reject paths containing
1136
+ // "..", starting with "/" or "\", or containing backslashes.
1137
+ if (assetPath.contains("..") ||
1138
+ assetPath.startsWith("/") ||
1139
+ assetPath.startsWith("\\") ||
1140
+ assetPath.contains("\\")) {
1141
+ promise.reject("ASSET_READ_ERROR", "Invalid asset path: $assetPath")
1142
+ return
1143
+ }
1144
+ try {
1145
+ val content = reactApplicationContext.assets.open(assetPath).bufferedReader().use { it.readText() }
1146
+ promise.resolve(content)
1147
+ } catch (e: Exception) {
1148
+ android.util.Log.e(NAME, "Failed to read asset $assetPath: ${e.message}", e)
1149
+ promise.reject("ASSET_READ_ERROR", "Failed to read asset $assetPath: ${e.message}", e)
1150
+ }
1151
+ }
1152
+
1000
1153
  companion object {
1001
1154
  const val NAME = "SherpaOnnx"
1002
1155
 
@@ -1039,5 +1192,11 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
1039
1192
  /** Convert any supported audio file to WAV 16 kHz mono 16-bit PCM. Returns empty string on success, error message otherwise. Requires FFmpeg prebuilts. */
1040
1193
  @JvmStatic
1041
1194
  private external fun nativeConvertAudioToWav16k(inputPath: String, outputPath: String): String
1195
+
1196
+ /**
1197
+ * On success: [FloatArray samples, Integer sampleRate]. On error: [String message].
1198
+ */
1199
+ @JvmStatic
1200
+ private external fun nativeDecodeAudioFileToFloatSamples(inputPath: String, targetSampleRateHz: Int): Array<Any>
1042
1201
  }
1043
1202
  }
@@ -132,6 +132,7 @@ internal class SherpaOnnxOnlineSttHelper(
132
132
  provider: String?,
133
133
  ruleFsts: String?,
134
134
  ruleFars: String?,
135
+ dither: Float?,
135
136
  blankPenalty: Float?,
136
137
  debug: Boolean?,
137
138
  rule1MustContainNonSilence: Boolean?,
@@ -233,7 +234,7 @@ internal class SherpaOnnxOnlineSttHelper(
233
234
  }
234
235
 
235
236
  return OnlineRecognizerConfig(
236
- featConfig = FeatureConfig(sampleRate = 16000, featureDim = 80, dither = 0f),
237
+ featConfig = FeatureConfig(sampleRate = 16000, featureDim = 80, dither = dither ?: 0f),
237
238
  modelConfig = modelConfig,
238
239
  endpointConfig = endpointConfig,
239
240
  enableEndpoint = enableEndpoint,
@@ -260,6 +261,7 @@ internal class SherpaOnnxOnlineSttHelper(
260
261
  provider: String?,
261
262
  ruleFsts: String?,
262
263
  ruleFars: String?,
264
+ dither: Double?,
263
265
  blankPenalty: Double?,
264
266
  debug: Boolean?,
265
267
  rule1MustContainNonSilence: Boolean?,
@@ -286,6 +288,7 @@ internal class SherpaOnnxOnlineSttHelper(
286
288
  provider = provider,
287
289
  ruleFsts = ruleFsts,
288
290
  ruleFars = ruleFars,
291
+ dither = dither?.toFloat(),
289
292
  blankPenalty = blankPenalty?.toFloat(),
290
293
  debug = debug,
291
294
  rule1MustContainNonSilence = rule1MustContainNonSilence,