@iternio/react-native-auto-play 0.4.8 → 0.4.11
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/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAutoPlay.kt +93 -0
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VoiceInputManager.kt +20 -286
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/utils/ThreadUtil.kt +13 -6
- package/ios/hybrid/HybridAutoPlay.swift +51 -2
- package/ios/utils/VoiceInputManager.swift +40 -144
- package/lib/index.d.ts +1 -3
- package/lib/index.js +1 -2
- package/lib/scenes/CarPlayDashboardScene.d.ts +1 -0
- package/lib/scenes/CarPlayDashboardScene.js +13 -7
- package/lib/specs/AutoPlay.nitro.d.ts +35 -0
- package/nitro.json +0 -10
- package/nitrogen/generated/android/ReactNativeAutoPlay+autolinking.cmake +0 -2
- package/nitrogen/generated/android/ReactNativeAutoPlayOnLoad.cpp +0 -18
- package/nitrogen/generated/android/c++/JActiveCarUxRestrictions.hpp +8 -8
- package/nitrogen/generated/android/c++/JGridTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JHybridAndroidAutoTelemetrySpec.cpp +4 -4
- package/nitrogen/generated/android/c++/JHybridAutoPlaySpec.cpp +52 -4
- package/nitrogen/generated/android/c++/JHybridAutoPlaySpec.hpp +5 -0
- package/nitrogen/generated/android/c++/JHybridClusterSpec.cpp +4 -4
- package/nitrogen/generated/android/c++/JHybridGridTemplateSpec.cpp +4 -4
- package/nitrogen/generated/android/c++/JHybridListTemplateSpec.cpp +4 -4
- package/nitrogen/generated/android/c++/JHybridMapTemplateSpec.cpp +15 -15
- package/nitrogen/generated/android/c++/JInformationTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JLaneGuidance.hpp +16 -16
- package/nitrogen/generated/android/c++/JListTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JMapTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JMessageTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JNitroAttributedString.hpp +8 -8
- package/nitrogen/generated/android/c++/JNitroBaseMapTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JNitroManeuver.cpp +4 -4
- package/nitrogen/generated/android/c++/JNitroManeuver.hpp +4 -4
- package/nitrogen/generated/android/c++/JNitroRoutingManeuver.hpp +16 -16
- package/nitrogen/generated/android/c++/JNitroSection.hpp +8 -8
- package/nitrogen/generated/android/c++/JPermissionRequestResult.hpp +16 -16
- package/nitrogen/generated/android/c++/JRouteChoice.hpp +32 -32
- package/nitrogen/generated/android/c++/JSearchTemplateConfig.hpp +8 -8
- package/nitrogen/generated/android/c++/JSignInTemplateConfig.hpp +16 -16
- package/nitrogen/generated/android/c++/JTripsConfig.hpp +8 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ActiveCarUxRestrictions.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/AppFocusState.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/AssetImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/AutoText.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/BooleanTelemetryItem.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Distance.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/DurationWithTimeZone.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GlyphImage.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GoogleSignIn.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GoogleSignInAccount.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GridTemplateConfig.kt +33 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAutoPlaySpec.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ImageLane.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/InformationTemplateConfig.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/InputSignIn.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/LaneGuidance.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ListTemplateConfig.kt +33 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Location.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/MapTemplateConfig.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/MessageTemplateConfig.kt +37 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NavigationAlertAction.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroAction.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroAttributedString.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroAttributedStringImage.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroBaseMapTemplateConfig.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroColor.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroGridButton.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroImage.kt +18 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroLoadingManeuver.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroManeuver.kt +18 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroMapButton.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroMessageManeuver.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroNavigationAlert.kt +31 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroRoutingManeuver.kt +49 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroRow.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroSection.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NumericTelemetryItem.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/PermissionRequestResult.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/PinSignIn.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Point.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/PreferredImageLane.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/QrSignIn.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/RemoteImage.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/RouteChoice.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/SafeAreaInsets.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/SearchTemplateConfig.kt +39 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/SignInTemplateConfig.kt +37 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/StringTelemetryItem.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Telemetry.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/TravelEstimates.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/TripConfig.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/TripPoint.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/TripPreviewTextConfiguration.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/TripSelectorCallback.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/TripsConfig.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Variant_GlyphImage_AssetImage_RemoteImage.kt +18 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Variant_PreferredImageLane_ImageLane.kt +16 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Variant_QrSignIn_PinSignIn_InputSignIn_GoogleSignIn.kt +20 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/VehicleTelemetryItem.kt +19 -0
- package/nitrogen/generated/ios/ReactNativeAutoPlay+autolinking.rb +2 -0
- package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Bridge.cpp +16 -41
- package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Bridge.hpp +126 -201
- package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Umbrella.hpp +0 -11
- package/nitrogen/generated/ios/ReactNativeAutoPlayAutolinking.mm +0 -8
- package/nitrogen/generated/ios/ReactNativeAutoPlayAutolinking.swift +0 -12
- package/nitrogen/generated/ios/c++/HybridAutoPlaySpecSwift.hpp +42 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
- package/nitrogen/generated/ios/swift/{Func_void_VoiceInputResult.swift → Func_void_std__shared_ptr_ArrayBuffer_.swift} +10 -10
- package/nitrogen/generated/ios/swift/HybridAutoPlaySpec.swift +5 -0
- package/nitrogen/generated/ios/swift/HybridAutoPlaySpec_cxx.swift +94 -0
- package/nitrogen/generated/ios/swift/NitroImage.swift +13 -0
- package/nitrogen/generated/ios/swift/NitroManeuver.swift +13 -0
- package/nitrogen/generated/ios/swift/Variant_GlyphImage_AssetImage_RemoteImage.swift +13 -0
- package/nitrogen/generated/ios/swift/Variant_PreferredImageLane_ImageLane.swift +12 -0
- package/nitrogen/generated/shared/c++/HybridAutoPlaySpec.cpp +5 -0
- package/nitrogen/generated/shared/c++/HybridAutoPlaySpec.hpp +6 -0
- package/package.json +2 -2
- package/src/index.ts +1 -3
- package/src/scenes/CarPlayDashboardScene.ts +18 -11
- package/src/specs/AutoPlay.nitro.ts +44 -0
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridVoice.kt +0 -97
- package/ios/hybrid/HybridVoice.swift +0 -65
- package/lib/HybridAutoPlay.d.ts +0 -2
- package/lib/HybridAutoPlay.js +0 -2
- package/lib/components/OnAppearedChildRenderer.d.ts +0 -10
- package/lib/components/OnAppearedChildRenderer.js +0 -26
- package/lib/hooks/useIsAutoPlayFocused.d.ts +0 -7
- package/lib/hooks/useIsAutoPlayFocused.js +0 -20
- package/lib/hybrid/HybridVoice.d.ts +0 -52
- package/lib/hybrid/HybridVoice.js +0 -52
- package/lib/hybrid.d.ts +0 -2
- package/lib/hybrid.js +0 -2
- package/lib/specs/AutomotivePermissionRequestTemplate.d.ts +0 -11
- package/lib/specs/AutomotivePermissionRequestTemplate.js +0 -1
- package/lib/specs/AutomotivePermissionRequestTemplate.nitro.d.ts +0 -11
- package/lib/specs/AutomotivePermissionRequestTemplate.nitro.js +0 -1
- package/lib/specs/Voice.nitro.d.ts +0 -11
- package/lib/specs/Voice.nitro.js +0 -1
- package/lib/templates/AutomotivePermissionRequestTemplate.d.ts +0 -23
- package/lib/templates/AutomotivePermissionRequestTemplate.js +0 -18
- package/lib/types/Glyphmap.d.ts +0 -4105
- package/lib/types/Glyphmap.js +0 -4105
- package/lib/types/Voice.d.ts +0 -16
- package/lib/types/Voice.js +0 -1
- package/nitrogen/generated/android/c++/JFunc_void_VoiceInputChunk.hpp +0 -81
- package/nitrogen/generated/android/c++/JHybridVoiceSpec.cpp +0 -104
- package/nitrogen/generated/android/c++/JHybridVoiceSpec.hpp +0 -66
- package/nitrogen/generated/android/c++/JVoiceInputChunk.hpp +0 -64
- package/nitrogen/generated/android/c++/JVoiceInputResult.hpp +0 -64
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/Func_void_VoiceInputChunk.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridVoiceSpec.kt +0 -72
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/VoiceInputChunk.kt +0 -41
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/VoiceInputResult.kt +0 -41
- package/nitrogen/generated/ios/c++/HybridVoiceSpecSwift.cpp +0 -11
- package/nitrogen/generated/ios/c++/HybridVoiceSpecSwift.hpp +0 -116
- package/nitrogen/generated/ios/swift/Func_void_VoiceInputChunk.swift +0 -46
- package/nitrogen/generated/ios/swift/HybridVoiceSpec.swift +0 -58
- package/nitrogen/generated/ios/swift/HybridVoiceSpec_cxx.swift +0 -234
- package/nitrogen/generated/ios/swift/VoiceInputChunk.swift +0 -60
- package/nitrogen/generated/ios/swift/VoiceInputResult.swift +0 -60
- package/nitrogen/generated/shared/c++/HybridVoiceSpec.cpp +0 -24
- package/nitrogen/generated/shared/c++/HybridVoiceSpec.hpp +0 -73
- package/nitrogen/generated/shared/c++/VoiceInputChunk.hpp +0 -89
- package/nitrogen/generated/shared/c++/VoiceInputResult.hpp +0 -89
- package/src/hybrid/HybridVoice.ts +0 -79
- package/src/specs/Voice.nitro.ts +0 -16
- package/src/types/Voice.ts +0 -18
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridAutoPlay.kt
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
package com.margelo.nitro.swe.iternio.reactnativeautoplay
|
|
2
2
|
|
|
3
|
+
import android.content.pm.PackageManager
|
|
3
4
|
import android.os.Build
|
|
5
|
+
import androidx.core.content.ContextCompat
|
|
4
6
|
import com.facebook.react.bridge.UiThreadUtil
|
|
7
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
8
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
9
|
+
import com.margelo.nitro.NitroModules
|
|
10
|
+
import com.margelo.nitro.core.ArrayBuffer
|
|
5
11
|
import com.margelo.nitro.core.Promise
|
|
6
12
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.template.AndroidAutoTemplate
|
|
7
13
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.template.MessageTemplate
|
|
8
14
|
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.ThreadUtil
|
|
15
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
16
|
+
import java.nio.ByteBuffer
|
|
9
17
|
import java.util.concurrent.ConcurrentHashMap
|
|
10
18
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
11
19
|
import kotlin.coroutines.resume
|
|
@@ -37,6 +45,10 @@ class HybridAutoPlay : HybridAutoPlaySpec() {
|
|
|
37
45
|
return AndroidAutoSession.getIsConnected()
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
override fun isCarServiceRunning(): Boolean {
|
|
49
|
+
return AndroidAutoService.instance != null
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
override fun addListenerRenderState(
|
|
41
53
|
moduleName: String, callback: (VisibilityState) -> Unit
|
|
42
54
|
): () -> Unit {
|
|
@@ -243,6 +255,84 @@ class HybridAutoPlay : HybridAutoPlaySpec() {
|
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
|
|
258
|
+
override fun hasVoiceInputPermission(): Boolean {
|
|
259
|
+
val context = NitroModules.applicationContext ?: return false
|
|
260
|
+
return ContextCompat.checkSelfPermission(
|
|
261
|
+
context, android.Manifest.permission.RECORD_AUDIO
|
|
262
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
override fun requestVoiceInputPermission(): Promise<Boolean> {
|
|
266
|
+
return Promise.async {
|
|
267
|
+
if (hasVoiceInputPermission()) {
|
|
268
|
+
return@async true
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
val carContext = AndroidAutoSession.getRootContext()
|
|
272
|
+
|
|
273
|
+
if (carContext != null) {
|
|
274
|
+
suspendCancellableCoroutine {
|
|
275
|
+
carContext.requestPermissions(listOf(android.Manifest.permission.RECORD_AUDIO)) { approved, _ ->
|
|
276
|
+
it.resume(approved.contains(android.Manifest.permission.RECORD_AUDIO))
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
val context = NitroModules.applicationContext ?: return@async false
|
|
281
|
+
val activity =
|
|
282
|
+
context.currentActivity as? PermissionAwareActivity ?: return@async false
|
|
283
|
+
val code = (Math.random() * 10000).toInt()
|
|
284
|
+
|
|
285
|
+
suspendCancellableCoroutine {
|
|
286
|
+
activity.requestPermissions(
|
|
287
|
+
arrayOf(android.Manifest.permission.RECORD_AUDIO),
|
|
288
|
+
code,
|
|
289
|
+
PermissionListener { requestCode, _, grantResults ->
|
|
290
|
+
if (requestCode != code) {
|
|
291
|
+
return@PermissionListener false
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
val granted =
|
|
295
|
+
grantResults.isNotEmpty() && grantResults.first() == PackageManager.PERMISSION_GRANTED
|
|
296
|
+
|
|
297
|
+
it.resume(granted)
|
|
298
|
+
|
|
299
|
+
return@PermissionListener true
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
override fun startVoiceInput(
|
|
307
|
+
silenceThresholdMs: Double?, maxDurationMs: Double?, listeningText: String?
|
|
308
|
+
): Promise<ArrayBuffer> {
|
|
309
|
+
return Promise.async {
|
|
310
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
311
|
+
throw UnsupportedOperationException("startVoiceInput requires at least API level ${Build.VERSION_CODES.O}")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
val manager = VoiceInputManager(AndroidAutoSession.getRootContext())
|
|
315
|
+
voiceInputManager = manager
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
val pcmBytes = manager.start(
|
|
319
|
+
silenceThresholdMs = silenceThresholdMs?.toLong() ?: 1_500L,
|
|
320
|
+
maxDurationMs = maxDurationMs?.toLong() ?: 10_000L,
|
|
321
|
+
)
|
|
322
|
+
val directBuffer =
|
|
323
|
+
ByteBuffer.allocateDirect(pcmBytes.size).put(pcmBytes).rewind() as ByteBuffer
|
|
324
|
+
ArrayBuffer.wrap(directBuffer)
|
|
325
|
+
} finally {
|
|
326
|
+
voiceInputManager = null
|
|
327
|
+
manager.dispose()
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
override fun stopVoiceInput() {
|
|
333
|
+
voiceInputManager?.stop()
|
|
334
|
+
}
|
|
335
|
+
|
|
246
336
|
companion object {
|
|
247
337
|
const val TAG = "HybridAutoPlay"
|
|
248
338
|
|
|
@@ -253,6 +343,9 @@ class HybridAutoPlay : HybridAutoPlaySpec() {
|
|
|
253
343
|
|
|
254
344
|
private val voiceInputListeners = CopyOnWriteArrayList<(Location?, String?) -> Unit>()
|
|
255
345
|
|
|
346
|
+
@Volatile
|
|
347
|
+
private var voiceInputManager: VoiceInputManager? = null
|
|
348
|
+
|
|
256
349
|
private val safeAreaInsetsListeners =
|
|
257
350
|
ConcurrentHashMap<String, CopyOnWriteArrayList<(SafeAreaInsets) -> Unit>>()
|
|
258
351
|
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VoiceInputManager.kt
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
package com.margelo.nitro.swe.iternio.reactnativeautoplay
|
|
2
2
|
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
|
-
import android.content.Intent
|
|
5
3
|
import android.content.pm.PackageManager
|
|
6
4
|
import android.media.AudioAttributes
|
|
7
5
|
import android.media.AudioFocusRequest
|
|
@@ -10,28 +8,18 @@ import android.media.AudioManager
|
|
|
10
8
|
import android.media.AudioRecord
|
|
11
9
|
import android.media.MediaRecorder
|
|
12
10
|
import android.os.Build
|
|
13
|
-
import android.os.Bundle
|
|
14
|
-
import android.os.ParcelFileDescriptor
|
|
15
|
-
import android.speech.RecognitionListener
|
|
16
|
-
import android.speech.RecognizerIntent
|
|
17
|
-
import android.speech.SpeechRecognizer
|
|
18
11
|
import androidx.annotation.RequiresApi
|
|
19
12
|
import androidx.car.app.CarContext
|
|
20
13
|
import androidx.car.app.media.CarAudioRecord
|
|
21
14
|
import androidx.core.content.ContextCompat
|
|
22
|
-
import com.facebook.react.bridge.UiThreadUtil
|
|
23
15
|
import com.margelo.nitro.NitroModules
|
|
24
|
-
import com.margelo.nitro.core.ArrayBuffer
|
|
25
|
-
import com.margelo.nitro.swe.iternio.reactnativeautoplay.utils.ThreadUtil
|
|
26
16
|
import kotlinx.coroutines.CoroutineScope
|
|
27
17
|
import kotlinx.coroutines.Dispatchers
|
|
28
18
|
import kotlinx.coroutines.Job
|
|
29
|
-
import kotlinx.coroutines.async
|
|
30
19
|
import kotlinx.coroutines.cancel
|
|
31
20
|
import kotlinx.coroutines.launch
|
|
32
21
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
33
22
|
import java.io.ByteArrayOutputStream
|
|
34
|
-
import java.nio.ByteBuffer
|
|
35
23
|
import kotlin.coroutines.Continuation
|
|
36
24
|
import kotlin.coroutines.resume
|
|
37
25
|
import kotlin.coroutines.resumeWithException
|
|
@@ -39,274 +27,43 @@ import kotlin.math.abs
|
|
|
39
27
|
|
|
40
28
|
/**
|
|
41
29
|
* Captures 16-bit PCM audio (16 kHz, mono).
|
|
42
|
-
* When [carContext] is provided uses CarAudioRecord (Android Auto/Automotive)
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* When preferSpeechToText is true and SpeechRecognizer is available, it owns
|
|
46
|
-
* the microphone and streams partial results; the PCM path is not used.
|
|
47
|
-
* When SpeechRecognizer is unavailable the manager falls back to PCM recording.
|
|
30
|
+
* When [carContext] is provided uses CarAudioRecord (Android Auto/Automotive).
|
|
31
|
+
* When [carContext] is null falls back to standard AudioRecord (phone-only).
|
|
48
32
|
*/
|
|
49
33
|
class VoiceInputManager(
|
|
50
34
|
private val carContext: CarContext?,
|
|
51
35
|
) {
|
|
52
|
-
// PCM recording state
|
|
53
36
|
private var carAudioRecord: CarAudioRecord? = null
|
|
54
37
|
private var audioRecord: AudioRecord? = null
|
|
55
38
|
private var audioFocusRequest: AudioFocusRequest? = null
|
|
56
39
|
private var recordingJob: Job? = null
|
|
57
|
-
private var
|
|
40
|
+
private var continuation: Continuation<ByteArray>? = null
|
|
58
41
|
private val scope = CoroutineScope(Dispatchers.IO)
|
|
59
42
|
|
|
60
43
|
@Volatile
|
|
61
44
|
private var isRecording = false
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Acquires audio focus, starts recording, and suspends until stopped.
|
|
48
|
+
* Stops automatically after [silenceThresholdMs] of silence or [maxDurationMs] total.
|
|
49
|
+
* Returns the complete raw PCM buffer (Int16 LE, 16 kHz, mono).
|
|
50
|
+
*/
|
|
67
51
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
68
52
|
suspend fun start(
|
|
69
53
|
silenceThresholdMs: Long = 1_500,
|
|
70
54
|
maxDurationMs: Long = 10_000,
|
|
71
|
-
preferSpeechToText: Boolean = false,
|
|
72
|
-
onChunk: ((chunk: VoiceInputChunk) -> Unit)? = null,
|
|
73
|
-
language: String? = null
|
|
74
|
-
): VoiceInputResult {
|
|
75
|
-
if (preferSpeechToText) {
|
|
76
|
-
val context = NitroModules.applicationContext ?: throw IllegalArgumentException()
|
|
77
|
-
if (SpeechRecognizer.isRecognitionAvailable(context)) {
|
|
78
|
-
if (carContext != null) {
|
|
79
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
80
|
-
return startSTTFromCarAudio(silenceThresholdMs, maxDurationMs, onChunk, language)
|
|
81
|
-
}
|
|
82
|
-
// Car connected but API < 33: EXTRA_AUDIO_SOURCE unavailable, fall back to PCM
|
|
83
|
-
return startPCM(silenceThresholdMs, maxDurationMs, onChunk)
|
|
84
|
-
}
|
|
85
|
-
return ThreadUtil.postOnUiAndAwait { startSTT(context, onChunk, language) }.getOrThrow()
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return startPCM(silenceThresholdMs, maxDurationMs, onChunk)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// MARK: - STT path (SpeechRecognizer owns the mic)
|
|
92
|
-
|
|
93
|
-
private suspend fun startSTT(
|
|
94
|
-
context: android.content.Context,
|
|
95
|
-
onChunk: ((chunk: VoiceInputChunk) -> Unit)?,
|
|
96
|
-
language: String?
|
|
97
|
-
): VoiceInputResult = suspendCancellableCoroutine { cont ->
|
|
98
|
-
val recognizer = SpeechRecognizer.createSpeechRecognizer(context)
|
|
99
|
-
activeSpeechRecognizer = recognizer
|
|
100
|
-
|
|
101
|
-
recognizer.setRecognitionListener(object : RecognitionListener {
|
|
102
|
-
override fun onResults(results: Bundle?) {
|
|
103
|
-
activeSpeechRecognizer = null
|
|
104
|
-
recognizer.destroy()
|
|
105
|
-
val text =
|
|
106
|
-
results?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)?.firstOrNull()
|
|
107
|
-
cont.resume(VoiceInputResult(transcription = text, audio = null))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
override fun onError(error: Int) {
|
|
111
|
-
activeSpeechRecognizer = null
|
|
112
|
-
recognizer.destroy()
|
|
113
|
-
// Return empty transcription — caller sees null audio and null transcription
|
|
114
|
-
cont.resume(VoiceInputResult(transcription = null, audio = null))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
override fun onPartialResults(partialResults: Bundle?) {
|
|
118
|
-
val text = partialResults?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
|
|
119
|
-
?.firstOrNull()
|
|
120
|
-
if (!text.isNullOrEmpty()) {
|
|
121
|
-
onChunk?.invoke(VoiceInputChunk(partial = text, audio = null))
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
override fun onReadyForSpeech(params: Bundle?) {}
|
|
126
|
-
override fun onBeginningOfSpeech() {}
|
|
127
|
-
override fun onRmsChanged(rmsdB: Float) {}
|
|
128
|
-
override fun onBufferReceived(buffer: ByteArray?) {}
|
|
129
|
-
override fun onEndOfSpeech() {}
|
|
130
|
-
override fun onEvent(eventType: Int, params: Bundle?) {}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
|
|
134
|
-
putExtra(
|
|
135
|
-
RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
|
136
|
-
)
|
|
137
|
-
putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
|
|
138
|
-
putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1)
|
|
139
|
-
language?.let {
|
|
140
|
-
putExtra(RecognizerIntent.EXTRA_LANGUAGE, it)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
recognizer.startListening(intent)
|
|
145
|
-
|
|
146
|
-
cont.invokeOnCancellation {
|
|
147
|
-
activeSpeechRecognizer = null
|
|
148
|
-
recognizer.destroy()
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// MARK: - STT path fed from CarAudioRecord via a pipe (API 33+)
|
|
153
|
-
@SuppressLint("MissingPermission")
|
|
154
|
-
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
|
155
|
-
private suspend fun startSTTFromCarAudio(
|
|
156
|
-
silenceThresholdMs: Long,
|
|
157
|
-
maxDurationMs: Long,
|
|
158
|
-
onChunk: ((chunk: VoiceInputChunk) -> Unit)?,
|
|
159
|
-
language: String?
|
|
160
|
-
): VoiceInputResult {
|
|
161
|
-
if (!hasVoiceInputPermission()) {
|
|
162
|
-
throw SecurityException("RECORD_AUDIO permission not granted")
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
val appContext = NitroModules.applicationContext ?: throw IllegalArgumentException()
|
|
166
|
-
val pipes = ParcelFileDescriptor.createPipe()
|
|
167
|
-
val readFd = pipes[0]
|
|
168
|
-
val pipeOut = ParcelFileDescriptor.AutoCloseOutputStream(pipes[1])
|
|
169
|
-
|
|
170
|
-
val sttDeferred = scope.async {
|
|
171
|
-
ThreadUtil.postOnUiAndAwait {
|
|
172
|
-
startSTTWithSource(appContext, readFd, silenceThresholdMs, onChunk, language)
|
|
173
|
-
}.getOrThrow()
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
recordPCM(silenceThresholdMs, maxDurationMs) { chunk ->
|
|
178
|
-
chunk.audio?.let { ab ->
|
|
179
|
-
try {
|
|
180
|
-
pipeOut.write(ab.toByteArray())
|
|
181
|
-
} catch (_: Exception) {
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
} finally {
|
|
186
|
-
try {
|
|
187
|
-
pipeOut.close()
|
|
188
|
-
} catch (_: Exception) {
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
readFd.close()
|
|
192
|
-
} catch (_: Exception) {
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return sttDeferred.await()
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
|
200
|
-
private suspend fun startSTTWithSource(
|
|
201
|
-
context: android.content.Context,
|
|
202
|
-
audioSource: ParcelFileDescriptor,
|
|
203
|
-
silenceThresholdMs: Long,
|
|
204
|
-
onChunk: ((chunk: VoiceInputChunk) -> Unit)?,
|
|
205
|
-
language: String?
|
|
206
|
-
): VoiceInputResult = suspendCancellableCoroutine { cont ->
|
|
207
|
-
val recognizer = SpeechRecognizer.createSpeechRecognizer(context)
|
|
208
|
-
activeSpeechRecognizer = recognizer
|
|
209
|
-
// When EXTRA_AUDIO_SOURCE is used, onResults always returns an empty list — the actual
|
|
210
|
-
// transcription only arrives via onPartialResults. Track the last partial here.
|
|
211
|
-
var lastPartial: String? = null
|
|
212
|
-
|
|
213
|
-
recognizer.setRecognitionListener(object : RecognitionListener {
|
|
214
|
-
override fun onResults(results: Bundle?) {
|
|
215
|
-
activeSpeechRecognizer = null
|
|
216
|
-
recognizer.destroy()
|
|
217
|
-
val text =
|
|
218
|
-
results?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)?.firstOrNull()
|
|
219
|
-
?: lastPartial
|
|
220
|
-
cont.resume(VoiceInputResult(transcription = text, audio = null))
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
override fun onError(error: Int) {
|
|
224
|
-
activeSpeechRecognizer = null
|
|
225
|
-
recognizer.destroy()
|
|
226
|
-
cont.resume(VoiceInputResult(transcription = lastPartial, audio = null))
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
override fun onPartialResults(partialResults: Bundle?) {
|
|
230
|
-
val text = partialResults?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
|
|
231
|
-
?.firstOrNull()
|
|
232
|
-
if (!text.isNullOrEmpty()) {
|
|
233
|
-
lastPartial = text
|
|
234
|
-
onChunk?.invoke(VoiceInputChunk(partial = text, audio = null))
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
override fun onReadyForSpeech(params: Bundle?) {}
|
|
239
|
-
override fun onBeginningOfSpeech() {}
|
|
240
|
-
override fun onRmsChanged(rmsdB: Float) {}
|
|
241
|
-
override fun onBufferReceived(buffer: ByteArray?) {}
|
|
242
|
-
override fun onEndOfSpeech() {}
|
|
243
|
-
override fun onEvent(eventType: Int, params: Bundle?) {}
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
|
|
247
|
-
putExtra(
|
|
248
|
-
RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
|
249
|
-
)
|
|
250
|
-
putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
|
|
251
|
-
putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1)
|
|
252
|
-
language?.let {
|
|
253
|
-
putExtra(RecognizerIntent.EXTRA_LANGUAGE, it)
|
|
254
|
-
}
|
|
255
|
-
putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE, audioSource)
|
|
256
|
-
putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_CHANNEL_COUNT, 1)
|
|
257
|
-
putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
|
|
258
|
-
putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_SAMPLING_RATE, SAMPLE_RATE)
|
|
259
|
-
putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, WARMUP_MS)
|
|
260
|
-
putExtra(
|
|
261
|
-
RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS,
|
|
262
|
-
silenceThresholdMs
|
|
263
|
-
)
|
|
264
|
-
putExtra(
|
|
265
|
-
RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS,
|
|
266
|
-
silenceThresholdMs / 2,
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
recognizer.startListening(intent)
|
|
271
|
-
|
|
272
|
-
cont.invokeOnCancellation {
|
|
273
|
-
activeSpeechRecognizer = null
|
|
274
|
-
recognizer.destroy()
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// MARK: - PCM path
|
|
279
|
-
|
|
280
|
-
@RequiresApi(Build.VERSION_CODES.O)
|
|
281
|
-
private suspend fun startPCM(
|
|
282
|
-
silenceThresholdMs: Long,
|
|
283
|
-
maxDurationMs: Long,
|
|
284
|
-
onChunk: ((chunk: VoiceInputChunk) -> Unit)?,
|
|
285
|
-
): VoiceInputResult {
|
|
286
|
-
val pcmBytes = recordPCM(silenceThresholdMs, maxDurationMs, onChunk)
|
|
287
|
-
val directBuffer =
|
|
288
|
-
ByteBuffer.allocateDirect(pcmBytes.size).put(pcmBytes).rewind() as ByteBuffer
|
|
289
|
-
return VoiceInputResult(transcription = null, audio = ArrayBuffer.wrap(directBuffer))
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
@SuppressLint("MissingPermission")
|
|
293
|
-
@RequiresApi(Build.VERSION_CODES.O)
|
|
294
|
-
private suspend fun recordPCM(
|
|
295
|
-
silenceThresholdMs: Long,
|
|
296
|
-
maxDurationMs: Long,
|
|
297
|
-
onChunk: ((chunk: VoiceInputChunk) -> Unit)?,
|
|
298
55
|
): ByteArray = suspendCancellableCoroutine { cont ->
|
|
299
|
-
|
|
56
|
+
val appContext = NitroModules.applicationContext
|
|
57
|
+
if (appContext == null || ContextCompat.checkSelfPermission(
|
|
58
|
+
appContext,
|
|
59
|
+
android.Manifest.permission.RECORD_AUDIO,
|
|
60
|
+
) != PackageManager.PERMISSION_GRANTED
|
|
61
|
+
) {
|
|
300
62
|
cont.resumeWithException(SecurityException("RECORD_AUDIO permission not granted"))
|
|
301
63
|
return@suspendCancellableCoroutine
|
|
302
64
|
}
|
|
303
65
|
|
|
304
|
-
|
|
305
|
-
cont.resumeWithException(SecurityException("Missing application context"))
|
|
306
|
-
return@suspendCancellableCoroutine
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
pcmContinuation = cont
|
|
66
|
+
continuation = cont
|
|
310
67
|
|
|
311
68
|
val audioManager = appContext.getSystemService(AudioManager::class.java)
|
|
312
69
|
|
|
@@ -323,7 +80,7 @@ class VoiceInputManager(
|
|
|
323
80
|
}.build()
|
|
324
81
|
|
|
325
82
|
if (audioManager.requestAudioFocus(focusRequest) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
|
326
|
-
|
|
83
|
+
continuation = null
|
|
327
84
|
cont.resumeWithException(IllegalStateException("Audio focus request denied"))
|
|
328
85
|
return@suspendCancellableCoroutine
|
|
329
86
|
}
|
|
@@ -379,13 +136,6 @@ class VoiceInputManager(
|
|
|
379
136
|
if (read > 0) {
|
|
380
137
|
outputStream.write(buffer, 0, read)
|
|
381
138
|
|
|
382
|
-
onChunk?.let { cb ->
|
|
383
|
-
val chunk = ByteArray(read) { buffer[it] }
|
|
384
|
-
val direct =
|
|
385
|
-
ByteBuffer.allocateDirect(read).put(chunk).rewind() as ByteBuffer
|
|
386
|
-
cb(VoiceInputChunk(partial = null, audio = ArrayBuffer.wrap(direct)))
|
|
387
|
-
}
|
|
388
|
-
|
|
389
139
|
val now = System.currentTimeMillis()
|
|
390
140
|
val elapsedMs = now - recordingStart
|
|
391
141
|
|
|
@@ -401,9 +151,7 @@ class VoiceInputManager(
|
|
|
401
151
|
val sample =
|
|
402
152
|
(buffer[i].toInt() and 0xFF) or (buffer[i + 1].toInt() shl 8)
|
|
403
153
|
val absSample = abs(sample.toShort().toInt())
|
|
404
|
-
if (absSample > peak)
|
|
405
|
-
peak = absSample
|
|
406
|
-
}
|
|
154
|
+
if (absSample > peak) peak = absSample
|
|
407
155
|
i += 2
|
|
408
156
|
}
|
|
409
157
|
|
|
@@ -422,21 +170,14 @@ class VoiceInputManager(
|
|
|
422
170
|
}
|
|
423
171
|
} finally {
|
|
424
172
|
releaseResources()
|
|
425
|
-
val
|
|
426
|
-
|
|
427
|
-
|
|
173
|
+
val capturedContinuation = continuation
|
|
174
|
+
continuation = null
|
|
175
|
+
capturedContinuation?.resume(outputStream.toByteArray())
|
|
428
176
|
}
|
|
429
177
|
}
|
|
430
178
|
}
|
|
431
179
|
|
|
432
180
|
fun stop() {
|
|
433
|
-
// STT path: stopListening() triggers onResults/onError which resolves the continuation
|
|
434
|
-
activeSpeechRecognizer?.let { recognizer ->
|
|
435
|
-
UiThreadUtil.runOnUiThread {
|
|
436
|
-
recognizer.stopListening()
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// PCM path and car-audio STT pump
|
|
440
181
|
isRecording = false
|
|
441
182
|
carAudioRecord?.stopRecording()
|
|
442
183
|
audioRecord?.stop()
|
|
@@ -469,12 +210,5 @@ class VoiceInputManager(
|
|
|
469
210
|
private const val WARMUP_MS = 500L
|
|
470
211
|
private const val SAMPLE_RATE = 16_000
|
|
471
212
|
private const val PHONE_BUFFER_SIZE = 3_200 // ~100ms at 16kHz/16-bit/mono
|
|
472
|
-
|
|
473
|
-
fun hasVoiceInputPermission(): Boolean {
|
|
474
|
-
val context = NitroModules.applicationContext ?: return false
|
|
475
|
-
return ContextCompat.checkSelfPermission(
|
|
476
|
-
context, android.Manifest.permission.RECORD_AUDIO
|
|
477
|
-
) == PackageManager.PERMISSION_GRANTED
|
|
478
|
-
}
|
|
479
213
|
}
|
|
480
214
|
}
|
package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/utils/ThreadUtil.kt
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
package com.margelo.nitro.swe.iternio.reactnativeautoplay.utils
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import kotlinx.coroutines.
|
|
3
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
4
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
5
|
+
import kotlin.coroutines.resume
|
|
5
6
|
|
|
6
7
|
object ThreadUtil {
|
|
7
|
-
suspend fun <T> postOnUiAndAwait(block:
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
suspend fun <T> postOnUiAndAwait(block: () -> T): Result<T> =
|
|
9
|
+
suspendCancellableCoroutine { cont ->
|
|
10
|
+
UiThreadUtil.runOnUiThread {
|
|
11
|
+
try {
|
|
12
|
+
val result = block()
|
|
13
|
+
cont.resume(Result.success(result))
|
|
14
|
+
} catch (e: Exception) {
|
|
15
|
+
cont.resume(Result.failure(e))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
10
18
|
}
|
|
11
|
-
}
|
|
12
19
|
}
|
|
@@ -21,7 +21,7 @@ class HybridAutoPlay: HybridAutoPlaySpec {
|
|
|
21
21
|
private static var listeners = [EventName: [StateListener]]()
|
|
22
22
|
private static var renderStateListeners = [String: [RenderStateListener]]()
|
|
23
23
|
private static var safeAreaInsetsListeners = [String: [SafeAreaListener]]()
|
|
24
|
-
|
|
24
|
+
private static var voiceInputManager: VoiceInputManager?
|
|
25
25
|
|
|
26
26
|
override init() {
|
|
27
27
|
HybridAutoPlay.listeners.removeAll()
|
|
@@ -83,6 +83,10 @@ class HybridAutoPlay: HybridAutoPlaySpec {
|
|
|
83
83
|
return SceneStore.isRootModuleConnected()
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
func isCarServiceRunning() throws -> Bool {
|
|
87
|
+
return SceneStore.isRootModuleConnected()
|
|
88
|
+
}
|
|
89
|
+
|
|
86
90
|
func addSafeAreaInsetsListener(
|
|
87
91
|
moduleName: String,
|
|
88
92
|
callback: @escaping (SafeAreaInsets) -> Void
|
|
@@ -119,10 +123,55 @@ class HybridAutoPlay: HybridAutoPlaySpec {
|
|
|
119
123
|
func addListenerVoiceInput(
|
|
120
124
|
callback: @escaping (Location?, String?) -> Void
|
|
121
125
|
) throws -> () -> Void {
|
|
122
|
-
// iOS does not use the OS-triggered voice input path — use
|
|
126
|
+
// iOS does not use the OS-triggered voice input path — use startVoiceInput() instead.
|
|
123
127
|
return {}
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
func hasVoiceInputPermission() throws -> Bool {
|
|
131
|
+
return AVAudioSession.sharedInstance().recordPermission == .granted
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func requestVoiceInputPermission() throws -> Promise<Bool> {
|
|
135
|
+
return Promise.async {
|
|
136
|
+
return await withCheckedContinuation { cont in
|
|
137
|
+
AVAudioSession.sharedInstance().requestRecordPermission { granted in
|
|
138
|
+
cont.resume(returning: granted)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func startVoiceInput(silenceThresholdMs: Double?, maxDurationMs: Double?, listeningText: String?) throws -> Promise<
|
|
145
|
+
ArrayBuffer
|
|
146
|
+
> {
|
|
147
|
+
return Promise.async {
|
|
148
|
+
let interfaceController = try? await RootModule.withInterfaceController { $0 }
|
|
149
|
+
|
|
150
|
+
let manager = VoiceInputManager()
|
|
151
|
+
HybridAutoPlay.voiceInputManager = manager
|
|
152
|
+
|
|
153
|
+
defer {
|
|
154
|
+
HybridAutoPlay.voiceInputManager = nil
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let data = try await manager.start(
|
|
158
|
+
interfaceController: interfaceController,
|
|
159
|
+
silenceThresholdMs: silenceThresholdMs ?? 1_500,
|
|
160
|
+
maxDurationMs: maxDurationMs ?? 10_000,
|
|
161
|
+
listeningText: listeningText ?? "Listening..."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return try ArrayBuffer.copy(data: data)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
func stopVoiceInput() throws {
|
|
169
|
+
Task { @MainActor in
|
|
170
|
+
let interfaceController = try? await RootModule.withInterfaceController { $0 }
|
|
171
|
+
HybridAutoPlay.voiceInputManager?.stop(interfaceController: interfaceController)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
126
175
|
// MARK: set/push/pop templates
|
|
127
176
|
func setRootTemplate(templateId: String) throws -> Promise<Void> {
|
|
128
177
|
return Promise.async {
|