@voltkit-rtc/react-native 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +1606 -2
- package/android/build.gradle +70 -0
- package/android/consumer-rules.pro +21 -0
- package/android/src/main/AndroidManifest.xml +25 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/AppVisibility.kt +67 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/AudioDevices.kt +67 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/AudioRouter.kt +487 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/CallServiceTypes.kt +120 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/NativeTimers.kt +106 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/NetworkWatch.kt +120 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/OutputChoice.kt +65 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/RtcConfig.kt +83 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/ScoLink.kt +61 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitCallService.kt +287 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitRtcInitProvider.kt +215 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitRtcModule.kt +313 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitRtcPackage.kt +30 -0
- package/android/src/main/res/drawable/voltkit_rtc_default_call_icon.xml +15 -0
- package/android/src/main/res/raw/voltkit_rtc_keep.xml +7 -0
- package/app.plugin.js +17 -0
- package/lib/commonjs/audio.js +215 -0
- package/lib/commonjs/audio.js.map +1 -0
- package/lib/commonjs/components.js +207 -0
- package/lib/commonjs/components.js.map +1 -0
- package/lib/commonjs/create-client.js +47 -0
- package/lib/commonjs/create-client.js.map +1 -0
- package/lib/commonjs/errors.js +99 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/globals.js +61 -0
- package/lib/commonjs/globals.js.map +1 -0
- package/lib/commonjs/index.js +234 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/local-media.js +392 -0
- package/lib/commonjs/local-media.js.map +1 -0
- package/lib/commonjs/native-session.js +314 -0
- package/lib/commonjs/native-session.js.map +1 -0
- package/lib/commonjs/native.js +99 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/permissions.js +166 -0
- package/lib/commonjs/permissions.js.map +1 -0
- package/lib/commonjs/platform/android.js +126 -0
- package/lib/commonjs/platform/android.js.map +1 -0
- package/lib/commonjs/platform/common.js +138 -0
- package/lib/commonjs/platform/common.js.map +1 -0
- package/lib/commonjs/platform/devices.js +151 -0
- package/lib/commonjs/platform/devices.js.map +1 -0
- package/lib/commonjs/platform/index.js +35 -0
- package/lib/commonjs/platform/index.js.map +1 -0
- package/lib/commonjs/platform/ios.js +54 -0
- package/lib/commonjs/platform/ios.js.map +1 -0
- package/lib/commonjs/platform/streams.js +273 -0
- package/lib/commonjs/platform/streams.js.map +1 -0
- package/lib/commonjs/platform/timers.js +71 -0
- package/lib/commonjs/platform/timers.js.map +1 -0
- package/lib/commonjs/platform/visibility.js +60 -0
- package/lib/commonjs/platform/visibility.js.map +1 -0
- package/lib/commonjs/preflight.js +77 -0
- package/lib/commonjs/preflight.js.map +1 -0
- package/lib/commonjs/provider.js +73 -0
- package/lib/commonjs/provider.js.map +1 -0
- package/lib/commonjs/specs/NativeVoltkitRtc.js +29 -0
- package/lib/commonjs/specs/NativeVoltkitRtc.js.map +1 -0
- package/lib/module/audio.js +207 -0
- package/lib/module/audio.js.map +1 -0
- package/lib/module/components.js +200 -0
- package/lib/module/components.js.map +1 -0
- package/lib/module/create-client.js +42 -0
- package/lib/module/create-client.js.map +1 -0
- package/lib/module/errors.js +87 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/globals.js +53 -0
- package/lib/module/globals.js.map +1 -0
- package/lib/module/index.js +33 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/local-media.js +387 -0
- package/lib/module/local-media.js.map +1 -0
- package/lib/module/native-session.js +309 -0
- package/lib/module/native-session.js.map +1 -0
- package/lib/module/native.js +87 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/permissions.js +160 -0
- package/lib/module/permissions.js.map +1 -0
- package/lib/module/platform/android.js +122 -0
- package/lib/module/platform/android.js.map +1 -0
- package/lib/module/platform/common.js +128 -0
- package/lib/module/platform/common.js.map +1 -0
- package/lib/module/platform/devices.js +142 -0
- package/lib/module/platform/devices.js.map +1 -0
- package/lib/module/platform/index.js +24 -0
- package/lib/module/platform/index.js.map +1 -0
- package/lib/module/platform/ios.js +49 -0
- package/lib/module/platform/ios.js.map +1 -0
- package/lib/module/platform/streams.js +268 -0
- package/lib/module/platform/streams.js.map +1 -0
- package/lib/module/platform/timers.js +67 -0
- package/lib/module/platform/timers.js.map +1 -0
- package/lib/module/platform/visibility.js +54 -0
- package/lib/module/platform/visibility.js.map +1 -0
- package/lib/module/preflight.js +72 -0
- package/lib/module/preflight.js.map +1 -0
- package/lib/module/provider.js +68 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/specs/NativeVoltkitRtc.js +37 -0
- package/lib/module/specs/NativeVoltkitRtc.js.map +1 -0
- package/lib/typescript/commonjs/audio.d.ts +58 -0
- package/lib/typescript/commonjs/audio.d.ts.map +1 -0
- package/lib/typescript/commonjs/components.d.ts +58 -0
- package/lib/typescript/commonjs/components.d.ts.map +1 -0
- package/lib/typescript/commonjs/create-client.d.ts +35 -0
- package/lib/typescript/commonjs/create-client.d.ts.map +1 -0
- package/lib/typescript/commonjs/errors.d.ts +34 -0
- package/lib/typescript/commonjs/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/globals.d.ts +16 -0
- package/lib/typescript/commonjs/globals.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +24 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/local-media.d.ts +86 -0
- package/lib/typescript/commonjs/local-media.d.ts.map +1 -0
- package/lib/typescript/commonjs/native-session.d.ts +38 -0
- package/lib/typescript/commonjs/native-session.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +18 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/permissions.d.ts +30 -0
- package/lib/typescript/commonjs/permissions.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/android.d.ts +4 -0
- package/lib/typescript/commonjs/platform/android.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/common.d.ts +101 -0
- package/lib/typescript/commonjs/platform/common.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/devices.d.ts +42 -0
- package/lib/typescript/commonjs/platform/devices.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/index.d.ts +6 -0
- package/lib/typescript/commonjs/platform/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/ios.d.ts +4 -0
- package/lib/typescript/commonjs/platform/ios.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/streams.d.ts +82 -0
- package/lib/typescript/commonjs/platform/streams.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/timers.d.ts +19 -0
- package/lib/typescript/commonjs/platform/timers.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/visibility.d.ts +11 -0
- package/lib/typescript/commonjs/platform/visibility.d.ts.map +1 -0
- package/lib/typescript/commonjs/preflight.d.ts +19 -0
- package/lib/typescript/commonjs/preflight.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider.d.ts +35 -0
- package/lib/typescript/commonjs/provider.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/NativeVoltkitRtc.d.ts +157 -0
- package/lib/typescript/commonjs/specs/NativeVoltkitRtc.d.ts.map +1 -0
- package/lib/typescript/module/audio.d.ts +58 -0
- package/lib/typescript/module/audio.d.ts.map +1 -0
- package/lib/typescript/module/components.d.ts +58 -0
- package/lib/typescript/module/components.d.ts.map +1 -0
- package/lib/typescript/module/create-client.d.ts +35 -0
- package/lib/typescript/module/create-client.d.ts.map +1 -0
- package/lib/typescript/module/errors.d.ts +34 -0
- package/lib/typescript/module/errors.d.ts.map +1 -0
- package/lib/typescript/module/globals.d.ts +16 -0
- package/lib/typescript/module/globals.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +24 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/local-media.d.ts +86 -0
- package/lib/typescript/module/local-media.d.ts.map +1 -0
- package/lib/typescript/module/native-session.d.ts +38 -0
- package/lib/typescript/module/native-session.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +18 -0
- package/lib/typescript/module/native.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/permissions.d.ts +30 -0
- package/lib/typescript/module/permissions.d.ts.map +1 -0
- package/lib/typescript/module/platform/android.d.ts +4 -0
- package/lib/typescript/module/platform/android.d.ts.map +1 -0
- package/lib/typescript/module/platform/common.d.ts +101 -0
- package/lib/typescript/module/platform/common.d.ts.map +1 -0
- package/lib/typescript/module/platform/devices.d.ts +42 -0
- package/lib/typescript/module/platform/devices.d.ts.map +1 -0
- package/lib/typescript/module/platform/index.d.ts +6 -0
- package/lib/typescript/module/platform/index.d.ts.map +1 -0
- package/lib/typescript/module/platform/ios.d.ts +4 -0
- package/lib/typescript/module/platform/ios.d.ts.map +1 -0
- package/lib/typescript/module/platform/streams.d.ts +82 -0
- package/lib/typescript/module/platform/streams.d.ts.map +1 -0
- package/lib/typescript/module/platform/timers.d.ts +19 -0
- package/lib/typescript/module/platform/timers.d.ts.map +1 -0
- package/lib/typescript/module/platform/visibility.d.ts +11 -0
- package/lib/typescript/module/platform/visibility.d.ts.map +1 -0
- package/lib/typescript/module/preflight.d.ts +19 -0
- package/lib/typescript/module/preflight.d.ts.map +1 -0
- package/lib/typescript/module/provider.d.ts +35 -0
- package/lib/typescript/module/provider.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeVoltkitRtc.d.ts +157 -0
- package/lib/typescript/module/specs/NativeVoltkitRtc.d.ts.map +1 -0
- package/package.json +140 -4
- package/plugin/build/android.d.ts +36 -0
- package/plugin/build/android.js +265 -0
- package/plugin/build/index.d.ts +9 -0
- package/plugin/build/index.js +54 -0
- package/plugin/build/ios.d.ts +4 -0
- package/plugin/build/ios.js +43 -0
- package/plugin/build/options.d.ts +93 -0
- package/plugin/build/options.js +140 -0
- package/react-native.config.js +11 -0
- package/src/audio.ts +221 -0
- package/src/components.tsx +233 -0
- package/src/create-client.ts +53 -0
- package/src/errors.ts +81 -0
- package/src/globals.ts +54 -0
- package/src/index.ts +95 -0
- package/src/local-media.ts +420 -0
- package/src/native-session.tsx +338 -0
- package/src/native.ts +84 -0
- package/src/permissions.ts +173 -0
- package/src/platform/android.ts +128 -0
- package/src/platform/common.ts +157 -0
- package/src/platform/devices.ts +148 -0
- package/src/platform/index.ts +22 -0
- package/src/platform/ios.ts +48 -0
- package/src/platform/streams.ts +285 -0
- package/src/platform/timers.ts +72 -0
- package/src/platform/visibility.ts +59 -0
- package/src/preflight.ts +60 -0
- package/src/provider.tsx +86 -0
- package/src/specs/NativeVoltkitRtc.ts +180 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
package com.voltkit.rtc.reactnative
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.pm.PackageManager
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import androidx.core.content.ContextCompat
|
|
11
|
+
import com.facebook.react.bridge.Arguments
|
|
12
|
+
import com.facebook.react.bridge.Promise
|
|
13
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
14
|
+
import com.facebook.react.bridge.ReadableMap
|
|
15
|
+
import com.facebook.react.bridge.WritableArray
|
|
16
|
+
import com.facebook.react.bridge.WritableMap
|
|
17
|
+
import com.oney.WebRTCModule.MediaProjectionService
|
|
18
|
+
import com.oney.WebRTCModule.WebRTCModuleOptions
|
|
19
|
+
import org.webrtc.audio.JavaAudioDeviceModule
|
|
20
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
21
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The TurboModule behind src/specs/NativeVoltkitRtc.ts (plan §5.1): audio routing ([AudioRouter]), the call service
|
|
25
|
+
* ([VoltkitCallService]), native timers ([NativeTimers]), visibility ([AppVisibility]), the network ([NetworkWatch])
|
|
26
|
+
* and stopping react-native-webrtc's screen-share service once a share ends (Phase 0 finding 5).
|
|
27
|
+
*
|
|
28
|
+
* When the React instance goes away (a reload, the app closing), [invalidate] lets go of everything this instance
|
|
29
|
+
* holds: its audio holders, timers, watchers and the call service, since the JavaScript that ran the call is gone.
|
|
30
|
+
* Rejections carry a VoltKit error registry code (`bad-params`, `media-unavailable`) as their code.
|
|
31
|
+
*/
|
|
32
|
+
class VoltkitRtcModule(reactContext: ReactApplicationContext) : NativeVoltkitRtcSpec(reactContext) {
|
|
33
|
+
private val app: Context = reactContext.applicationContext
|
|
34
|
+
private val config = VoltkitRtcInitProvider.configOf(app)
|
|
35
|
+
private val main = Handler(Looper.getMainLooper())
|
|
36
|
+
|
|
37
|
+
// --- audio ---------------------------------------------------------------------------------------------------
|
|
38
|
+
private val router: AudioRouter by lazy { AudioRouter.get(app, config.audioMode).also { it.addListener(audioListener) } }
|
|
39
|
+
private val holders = HashMap<Int, Any>()
|
|
40
|
+
private val nextHolder = AtomicInteger(0)
|
|
41
|
+
private val audioListener = object : AudioRouter.Listener {
|
|
42
|
+
override fun onDevicesChanged(change: DevicesChanged) {
|
|
43
|
+
emitOnAudioDevicesChanged(
|
|
44
|
+
Arguments.createMap().apply {
|
|
45
|
+
putArray("devices", devicesArray(change.devices))
|
|
46
|
+
putArray("added", devicesArray(change.added))
|
|
47
|
+
putArray("removed", devicesArray(change.removed))
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun onOutputChanged(device: MediaDevice?) {
|
|
53
|
+
emitOnAudioOutputChanged(Arguments.createMap().apply { putDevice("device", device) })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun onFocusChanged(focus: String) {
|
|
57
|
+
emitOnAudioFocusChanged(Arguments.createMap().apply { putString("focus", focus) })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
private val microphoneError: (String) -> Unit = { message ->
|
|
61
|
+
emitOnMicrophoneError(Arguments.createMap().apply { putString("message", message) })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --- timers, visibility, network, the call service --------------------------------------------------------------
|
|
65
|
+
private val timerThread = HandlerScheduler("VoltKitTimers")
|
|
66
|
+
private val timers = NativeTimers(timerThread) { id -> emitOnTimer(id) }
|
|
67
|
+
private val network = NetworkWatch(app) { handover ->
|
|
68
|
+
emitOnNetworkAvailable(Arguments.createMap().apply { putBoolean("handover", handover) })
|
|
69
|
+
}
|
|
70
|
+
private val stopVisibility: () -> Unit = AppVisibility.watch { visible ->
|
|
71
|
+
emitOnAppVisibilityChanged(Arguments.createMap().apply { putBoolean("visible", visible) })
|
|
72
|
+
}
|
|
73
|
+
private val callServiceListener = VoltkitCallService.Listener { type, types, message ->
|
|
74
|
+
emitOnCallServiceEvent(
|
|
75
|
+
Arguments.createMap().apply {
|
|
76
|
+
putString("type", type)
|
|
77
|
+
putArray("types", Arguments.fromList(CallServiceTypes.names(types)))
|
|
78
|
+
putString("message", message)
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
private val invalidated = AtomicBoolean(false)
|
|
83
|
+
|
|
84
|
+
init {
|
|
85
|
+
network.start()
|
|
86
|
+
VoltkitRtcInitProvider.microphoneErrors.add(microphoneError)
|
|
87
|
+
VoltkitCallService.listeners.add(callServiceListener)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override fun getTypedExportedConstants(): MutableMap<String, Any> {
|
|
91
|
+
val declared = VoltkitCallService.declaredTypes(app)
|
|
92
|
+
return hashMapOf(
|
|
93
|
+
"audioMode" to config.audioMode.name,
|
|
94
|
+
"hardwareEchoCancellation" to config.hardwareEchoCancellation,
|
|
95
|
+
"screenShareEnabled" to (config.screenShare || WebRTCModuleOptions.getInstance().enableMediaProjectionService),
|
|
96
|
+
"callServiceDeclared" to (declared != null),
|
|
97
|
+
"callServiceTypes" to CallServiceTypes.names(declared ?: 0),
|
|
98
|
+
"sdkInt" to Build.VERSION.SDK_INT,
|
|
99
|
+
"nativeVersion" to BuildConfig.VOLTKIT_RTC_VERSION,
|
|
100
|
+
// True only while react-native-webrtc uses the module the init provider made (the audio mode and HW_AEC apply).
|
|
101
|
+
"audioDeviceModule" to VoltkitRtcInitProvider.audioDeviceModule.let {
|
|
102
|
+
it != null && it === WebRTCModuleOptions.getInstance().audioDeviceModule
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- audio ---------------------------------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
override fun audioAcquire(speakerphone: Boolean?, promise: Promise) {
|
|
110
|
+
val id = nextHolder.incrementAndGet()
|
|
111
|
+
val token = Any()
|
|
112
|
+
synchronized(holders) { holders[id] = token }
|
|
113
|
+
router.acquire(token, speakerphone) { promise.resolve(id) }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun audioRelease(holder: Double) {
|
|
117
|
+
val token = synchronized(holders) { holders.remove(holder.toInt()) } ?: return
|
|
118
|
+
router.release(token)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun setSpeakerphoneOn(on: Boolean) {
|
|
122
|
+
router.setSpeakerphoneOn(on)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun listAudioDevices(promise: Promise) {
|
|
126
|
+
router.list { result ->
|
|
127
|
+
result.fold({ promise.resolve(devicesArray(it)) }, { promise.reject(E_MEDIA, "could not list the audio devices: ${it.message}", it) })
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override fun selectAudioOutput(deviceId: String, promise: Promise) {
|
|
132
|
+
// In media mode sound follows the system's media routing: there is nothing to choose.
|
|
133
|
+
if (!router.isRouting) return promise.resolve(false)
|
|
134
|
+
router.selectOutput(deviceId) { result ->
|
|
135
|
+
result.fold({ promise.resolve(it) }, { promise.reject(E_MEDIA, "could not route call audio: ${it.message}", it) })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
override fun selectAudioInput(deviceId: String?, promise: Promise) {
|
|
140
|
+
val adm = audioDeviceModule() ?: return promise.resolve(false)
|
|
141
|
+
if (deviceId == null) return promise.resolve(PreferredInput.clear(adm))
|
|
142
|
+
router.inputInfo(deviceId) { result ->
|
|
143
|
+
result.fold(
|
|
144
|
+
{ info -> promise.resolve(info != null && PreferredInput.choose(adm, info)) },
|
|
145
|
+
{ promise.reject(E_MEDIA, "could not choose the microphone: ${it.message}", it) },
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
override fun currentAudioOutput(promise: Promise) {
|
|
151
|
+
promise.resolve(router.output?.let(::deviceMap))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The audio device module react-native-webrtc plays and records with, when it is a JavaAudioDeviceModule: the init
|
|
156
|
+
* provider's, or one the app set itself. Never the provider's once the app replaced it, since WebRTC does not use it.
|
|
157
|
+
*/
|
|
158
|
+
private fun audioDeviceModule(): JavaAudioDeviceModule? =
|
|
159
|
+
WebRTCModuleOptions.getInstance().audioDeviceModule as? JavaAudioDeviceModule
|
|
160
|
+
|
|
161
|
+
// --- visibility ----------------------------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
override fun isAppVisible(): Boolean = AppVisibility.visible
|
|
164
|
+
|
|
165
|
+
// --- the call service ----------------------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
override fun startCallService(options: ReadableMap, promise: Promise) = callService(options, start = true, promise)
|
|
168
|
+
|
|
169
|
+
override fun updateCallService(options: ReadableMap, promise: Promise) = callService(options, start = false, promise)
|
|
170
|
+
|
|
171
|
+
override fun stopCallService(promise: Promise) {
|
|
172
|
+
main.post {
|
|
173
|
+
VoltkitCallService.stop(app)
|
|
174
|
+
promise.resolve(callServiceStatus(running = false))
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
override fun getCallServiceStatus(): WritableMap = callServiceStatus()
|
|
179
|
+
|
|
180
|
+
private fun callService(options: ReadableMap, start: Boolean, promise: Promise) {
|
|
181
|
+
val declared = VoltkitCallService.declaredTypes(app)
|
|
182
|
+
?: return promise.reject(
|
|
183
|
+
E_MEDIA,
|
|
184
|
+
"the app's manifest does not declare VoltkitCallService: turn on the Expo plugin's callService option, or add its <service> block (see the docs)",
|
|
185
|
+
)
|
|
186
|
+
val decision = CallServiceTypes.decide(
|
|
187
|
+
CallServiceTypes.Input(
|
|
188
|
+
sdkInt = Build.VERSION.SDK_INT,
|
|
189
|
+
declaredTypes = declared,
|
|
190
|
+
granted = grantedPermissions(),
|
|
191
|
+
audioMode = config.audioMode,
|
|
192
|
+
micPublished = options.flag("micPublished", true),
|
|
193
|
+
cameraPublished = options.flag("cameraPublished", false),
|
|
194
|
+
pauseCamera = options.flag("pauseCamera", true),
|
|
195
|
+
sharing = options.flag("sharing", false),
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
if (!decision.canStart) return promise.reject(E_MEDIA, decision.reason ?: "the call service cannot run")
|
|
199
|
+
val request = VoltkitCallService.Request(
|
|
200
|
+
title = options.text("title", "Call in progress"),
|
|
201
|
+
text = options.text("text", "Tap to return to the call"),
|
|
202
|
+
leaveLabel = options.text("leaveLabel", "Leave"),
|
|
203
|
+
types = decision.types,
|
|
204
|
+
)
|
|
205
|
+
val done: (Result<Int>) -> Unit = { result ->
|
|
206
|
+
result.fold({ promise.resolve(callServiceStatus()) }, { promise.reject(E_MEDIA, it.message ?: "the call service could not start", it) })
|
|
207
|
+
}
|
|
208
|
+
main.post {
|
|
209
|
+
when {
|
|
210
|
+
VoltkitCallService.running != null -> VoltkitCallService.update(request, done)
|
|
211
|
+
!start -> promise.resolve(callServiceStatus())
|
|
212
|
+
// Android 12+ refuses a foreground service started from the background; say so plainly.
|
|
213
|
+
Build.VERSION.SDK_INT >= 31 && !AppVisibility.visible ->
|
|
214
|
+
promise.reject(E_MEDIA, "the app is in the background: Android lets the call service start only while the app is visible")
|
|
215
|
+
else -> VoltkitCallService.start(app, request, done)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private fun callServiceStatus(running: Boolean = VoltkitCallService.running != null): WritableMap = Arguments.createMap().apply {
|
|
221
|
+
putBoolean("declared", VoltkitCallService.declaredTypes(app) != null)
|
|
222
|
+
putBoolean("running", running)
|
|
223
|
+
putArray("types", Arguments.fromList(if (running) CallServiceTypes.names(VoltkitCallService.runningTypes) else emptyList<String>()))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private fun grantedPermissions(): Set<String> =
|
|
227
|
+
CALL_SERVICE_PERMISSIONS.filter { ContextCompat.checkSelfPermission(app, it) == PackageManager.PERMISSION_GRANTED }.toSet()
|
|
228
|
+
|
|
229
|
+
// --- screen share --------------------------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
override fun stopScreenShareService(promise: Promise) {
|
|
232
|
+
// react-native-webrtc stops its MediaProjectionService only when the screen track is released, and a stopped
|
|
233
|
+
// track is not released: the service outlived every share (Phase 0 finding 5).
|
|
234
|
+
val stopped = try {
|
|
235
|
+
app.stopService(Intent(app, MediaProjectionService::class.java))
|
|
236
|
+
} catch (_: Throwable) {
|
|
237
|
+
false
|
|
238
|
+
}
|
|
239
|
+
promise.resolve(stopped)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// --- native timers -------------------------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
override fun timerStart(id: Double, ms: Double, repeat: Boolean) {
|
|
245
|
+
if (invalidated.get()) return
|
|
246
|
+
timers.start(id, ms, repeat)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
override fun timerStop(id: Double) {
|
|
250
|
+
timers.stop(id)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// --- the React instance went away ----------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
override fun invalidate() {
|
|
256
|
+
if (invalidated.compareAndSet(false, true)) {
|
|
257
|
+
timers.stopAll()
|
|
258
|
+
timerThread.quit()
|
|
259
|
+
stopVisibility()
|
|
260
|
+
network.stop()
|
|
261
|
+
VoltkitRtcInitProvider.microphoneErrors.remove(microphoneError)
|
|
262
|
+
VoltkitCallService.listeners.remove(callServiceListener)
|
|
263
|
+
val held = synchronized(holders) { holders.values.toList().also { holders.clear() } }
|
|
264
|
+
AudioRouter.existing()?.let { r ->
|
|
265
|
+
held.forEach { r.release(it) }
|
|
266
|
+
r.removeListener(audioListener)
|
|
267
|
+
}
|
|
268
|
+
main.post { VoltkitCallService.stop(app) }
|
|
269
|
+
}
|
|
270
|
+
super.invalidate()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// --- JavaScript values ---------------------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
private fun deviceMap(device: MediaDevice): WritableMap = Arguments.createMap().apply {
|
|
276
|
+
putString("deviceId", device.deviceId)
|
|
277
|
+
putString("kind", device.kind.name)
|
|
278
|
+
putString("label", device.label)
|
|
279
|
+
putString("type", (device.type ?: AudioDeviceType.other).name)
|
|
280
|
+
putString("groupId", device.groupId)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private fun devicesArray(devices: List<MediaDevice>): WritableArray = Arguments.createArray().apply {
|
|
284
|
+
devices.forEach { pushMap(deviceMap(it)) }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private fun WritableMap.putDevice(key: String, device: MediaDevice?) {
|
|
288
|
+
if (device == null) putNull(key) else putMap(key, deviceMap(device))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun ReadableMap.flag(key: String, default: Boolean): Boolean =
|
|
292
|
+
if (hasKey(key) && !isNull(key)) getBoolean(key) else default
|
|
293
|
+
|
|
294
|
+
private fun ReadableMap.text(key: String, default: String): String =
|
|
295
|
+
if (hasKey(key) && !isNull(key)) getString(key)?.takeIf { it.isNotBlank() } ?: default else default
|
|
296
|
+
|
|
297
|
+
companion object {
|
|
298
|
+
const val NAME = NativeVoltkitRtcSpec.NAME
|
|
299
|
+
|
|
300
|
+
/** Rejections use the VoltKit error registry's codes. */
|
|
301
|
+
private const val E_MEDIA = "media-unavailable"
|
|
302
|
+
|
|
303
|
+
private val CALL_SERVICE_PERMISSIONS = listOf(
|
|
304
|
+
Manifest.permission.FOREGROUND_SERVICE,
|
|
305
|
+
Manifest.permission.RECORD_AUDIO,
|
|
306
|
+
Manifest.permission.CAMERA,
|
|
307
|
+
Manifest.permission.FOREGROUND_SERVICE_MICROPHONE,
|
|
308
|
+
Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK,
|
|
309
|
+
Manifest.permission.FOREGROUND_SERVICE_CAMERA,
|
|
310
|
+
Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION,
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.voltkit.rtc.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What autolinking adds to the app's package list: the one TurboModule, VoltkitRtc (New Architecture only; React
|
|
11
|
+
* Native 0.82 and later have no other). Expo's and the React Native CLI's autolinking find this class by its
|
|
12
|
+
* declaration, `class VoltkitRtcPackage : BaseReactPackage()`.
|
|
13
|
+
*/
|
|
14
|
+
class VoltkitRtcPackage : BaseReactPackage() {
|
|
15
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
|
16
|
+
if (name == VoltkitRtcModule.NAME) VoltkitRtcModule(reactContext) else null
|
|
17
|
+
|
|
18
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
|
|
19
|
+
mapOf(
|
|
20
|
+
VoltkitRtcModule.NAME to ReactModuleInfo(
|
|
21
|
+
name = VoltkitRtcModule.NAME,
|
|
22
|
+
className = VoltkitRtcModule::class.java.name,
|
|
23
|
+
canOverrideExistingModule = false,
|
|
24
|
+
needsEagerInit = false,
|
|
25
|
+
isCxxModule = false,
|
|
26
|
+
isTurboModule = true,
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
The call notification's small icon when the app ships none of its own (VoltkitCallService.smallIcon): a white handset.
|
|
4
|
+
Android draws a small icon from its alpha channel only, so it must be a shape on transparency, never the launcher
|
|
5
|
+
icon. An app chooses its own by adding a drawable named voltkit_call_notification_icon.
|
|
6
|
+
-->
|
|
7
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
8
|
+
android:width="24dp"
|
|
9
|
+
android:height="24dp"
|
|
10
|
+
android:viewportWidth="24"
|
|
11
|
+
android:viewportHeight="24">
|
|
12
|
+
<path
|
|
13
|
+
android:fillColor="#FFFFFFFF"
|
|
14
|
+
android:pathData="M4.5,3L8.2,3C8.9,3 9.3,3.5 9.4,4.1L9.9,7.2C10,7.8 9.8,8.4 9.3,8.8L7.7,10.1C9,12.6 11.4,15 13.9,16.3L15.2,14.7C15.6,14.2 16.2,14 16.8,14.1L19.9,14.6C20.5,14.7 21,15.1 21,15.8L21,19.5C21,20.3 20.3,21 19.5,21C10.4,21 3,13.6 3,4.5C3,3.7 3.7,3 4.5,3Z" />
|
|
15
|
+
</vector>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
The call notification's icon is looked up by name in the app (VoltkitCallService.smallIcon), which resource shrinking
|
|
4
|
+
cannot see: keep the app's drawables of those names, when it has them. The SDK's own default is referenced from code.
|
|
5
|
+
-->
|
|
6
|
+
<resources xmlns:tools="http://schemas.android.com/tools"
|
|
7
|
+
tools:keep="@drawable/voltkit_call_notification_icon,@drawable/notification_icon,@drawable/ic_notification" />
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The Expo config plugin of @voltkit-rtc/react-native: Expo loads this file when app.json lists the package in
|
|
2
|
+
// "plugins". The plugin is TypeScript in plugin/src, built to plugin/build by `npm run build:plugin` (and by
|
|
3
|
+
// prepublishOnly, so a published package always carries it); see plugin/src/index.ts for what it writes.
|
|
4
|
+
try {
|
|
5
|
+
module.exports = require('./plugin/build');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
// plugin/build is build output (git-ignored): in a checkout it has to be built first. Anything else (a missing expo,
|
|
8
|
+
// an error inside the build) is reported as it is.
|
|
9
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes("Cannot find module './plugin/build'")) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"@voltkit-rtc/react-native: the Expo config plugin's build, plugin/build, is missing. A published package carries " +
|
|
12
|
+
'it; in a checkout run `npm run build:plugin` in react-native-sdk first.',
|
|
13
|
+
{ cause: error },
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.audioHoldOf = audioHoldOf;
|
|
7
|
+
exports.selectAudioOutput = selectAudioOutput;
|
|
8
|
+
exports.setSpeakerphoneOn = setSpeakerphoneOn;
|
|
9
|
+
exports.speakerPreference = speakerPreference;
|
|
10
|
+
exports.useAudioOutput = useAudioOutput;
|
|
11
|
+
var _react = require("react");
|
|
12
|
+
var _reactNative = require("react-native");
|
|
13
|
+
var _client = require("@voltkit-rtc/client");
|
|
14
|
+
var _native = require("./native.js");
|
|
15
|
+
var _errors = require("./errors.js");
|
|
16
|
+
/**
|
|
17
|
+
* Call audio on Android: the speaker or the earpiece, headsets and Bluetooth, through this package's audio router (a
|
|
18
|
+
* port of the Android SDK's AudioRouter, AudioSwitch's rules). Process-wide, like Android's AudioManager: every
|
|
19
|
+
* connected client holds the call audio (the provider does it), and the last to let go puts the device's audio mode,
|
|
20
|
+
* speakerphone and microphone mute back as it found them. setSpeakerphoneOn and selectAudioOutput work before any
|
|
21
|
+
* client exists: the choice is kept for the call.
|
|
22
|
+
*
|
|
23
|
+
* In the audio mode `media` (the Expo plugin's android.audioMode, for apps whose users only listen) sound follows the
|
|
24
|
+
* system's media routing: there is no earpiece and nothing to choose. On iOS (preview) there is no routing at all.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Whether this app chose the speaker itself (setSpeakerphoneOn): the provider then leaves that choice alone. */
|
|
28
|
+
let speakerChosen = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Play call audio on the loudspeaker (true) or the earpiece (false) when no headset is connected; a wired or Bluetooth
|
|
32
|
+
* headset still wins. Kept until changed, and applied to calls joined later. Rejects with `media-unavailable` on iOS
|
|
33
|
+
* and where the native module is missing.
|
|
34
|
+
*/
|
|
35
|
+
async function setSpeakerphoneOn(on) {
|
|
36
|
+
const module = (0, _native.requireNative)('Choosing the speaker or the earpiece');
|
|
37
|
+
module.setSpeakerphoneOn(Boolean(on));
|
|
38
|
+
speakerChosen = Boolean(on);
|
|
39
|
+
outputs.preferenceChanged();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Play call audio on this output (a deviceId from useAudioOutput().outputs) while it stays connected. Rejects with
|
|
44
|
+
* `bad-params` for an output that is not there and in the audio mode `media`, and with `media-unavailable` on iOS and
|
|
45
|
+
* where the native module is missing.
|
|
46
|
+
*/
|
|
47
|
+
async function selectAudioOutput(deviceId) {
|
|
48
|
+
const module = (0, _native.requireNative)('Choosing the audio output');
|
|
49
|
+
let routed;
|
|
50
|
+
try {
|
|
51
|
+
routed = await module.selectAudioOutput(deviceId);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw (0, _errors.nativeFailure)(err);
|
|
54
|
+
}
|
|
55
|
+
if (!routed) {
|
|
56
|
+
throw new _client.RpcError('bad-params', (0, _native.nativeConstants)()?.audioMode === 'media' ? 'in the audio mode media, sound follows the system\'s media routing: there is no output to choose' : `there is no audio output with the id ${deviceId}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The speaker preference a newly joined call holds the audio with: the provider's (default the loudspeaker), unless
|
|
62
|
+
* the app chose with setSpeakerphoneOn, which the router keeps (null).
|
|
63
|
+
*/
|
|
64
|
+
function speakerPreference(provider) {
|
|
65
|
+
return speakerChosen !== null ? null : provider ?? true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ------------------------------------------------------------------------------------------- holding the call audio
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One client's hold on the call audio: acquired once it is connected, released when it closes or the provider lets go
|
|
72
|
+
* of it. Released a moment late, so React's StrictMode (an effect unmounted and mounted again at once) neither
|
|
73
|
+
* releases nor acquires twice.
|
|
74
|
+
*/
|
|
75
|
+
class AudioHold {
|
|
76
|
+
holder = null;
|
|
77
|
+
users = 0;
|
|
78
|
+
pendingRelease = false;
|
|
79
|
+
constructor(onError) {
|
|
80
|
+
this.onError = onError;
|
|
81
|
+
}
|
|
82
|
+
retain() {
|
|
83
|
+
this.users += 1;
|
|
84
|
+
this.pendingRelease = false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** One user less; the hold goes when none is left. */
|
|
88
|
+
unretain() {
|
|
89
|
+
this.users = Math.max(0, this.users - 1);
|
|
90
|
+
if (this.users > 0) return;
|
|
91
|
+
this.pendingRelease = true;
|
|
92
|
+
queueMicrotask(() => {
|
|
93
|
+
if (this.pendingRelease && this.users === 0) this.release();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
acquire(speakerphone) {
|
|
97
|
+
if (this.holder) return;
|
|
98
|
+
const module = (0, _native.nativeModule)();
|
|
99
|
+
if (!module) return;
|
|
100
|
+
this.holder = module.audioAcquire(speakerphone).then(id => id, err => {
|
|
101
|
+
this.onError((0, _errors.nativeFailure)(err));
|
|
102
|
+
return null;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
release() {
|
|
106
|
+
this.pendingRelease = false;
|
|
107
|
+
const holder = this.holder;
|
|
108
|
+
if (!holder) return;
|
|
109
|
+
this.holder = null;
|
|
110
|
+
void holder.then(id => {
|
|
111
|
+
if (id !== null) (0, _native.nativeModule)()?.audioRelease(id);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const holds = new WeakMap();
|
|
116
|
+
|
|
117
|
+
/** The call audio hold of `client`. */
|
|
118
|
+
function audioHoldOf(client, onError) {
|
|
119
|
+
let hold = holds.get(client);
|
|
120
|
+
if (!hold) {
|
|
121
|
+
hold = new AudioHold(onError);
|
|
122
|
+
holds.set(client, hold);
|
|
123
|
+
}
|
|
124
|
+
return hold;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ------------------------------------------------------------------------------------------- useAudioOutput
|
|
128
|
+
|
|
129
|
+
const NONE = Object.freeze({
|
|
130
|
+
supported: false,
|
|
131
|
+
outputs: Object.freeze([]),
|
|
132
|
+
current: null,
|
|
133
|
+
speakerphone: false
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
/** The audio outputs, kept current from the router's events while something listens. */
|
|
137
|
+
class OutputStore {
|
|
138
|
+
state = NONE;
|
|
139
|
+
listeners = new Set();
|
|
140
|
+
stop = null;
|
|
141
|
+
subscribe = listener => {
|
|
142
|
+
this.listeners.add(listener);
|
|
143
|
+
if (this.listeners.size === 1) this.start();
|
|
144
|
+
return () => {
|
|
145
|
+
this.listeners.delete(listener);
|
|
146
|
+
if (this.listeners.size === 0) {
|
|
147
|
+
this.stop?.();
|
|
148
|
+
this.stop = null;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
snapshot = () => this.state;
|
|
153
|
+
|
|
154
|
+
/** The app changed the speaker preference: what is shown before a call holds the audio follows it. */
|
|
155
|
+
preferenceChanged() {
|
|
156
|
+
if (this.listeners.size > 0) this.set({
|
|
157
|
+
...this.state,
|
|
158
|
+
speakerphone: this.state.current ? this.state.current.type === 'speaker' : speakerChosen ?? true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
start() {
|
|
162
|
+
const module = (0, _native.nativeModule)();
|
|
163
|
+
const supported = _reactNative.Platform.OS === 'android' && module !== null && (0, _native.nativeConstants)()?.audioMode !== 'media';
|
|
164
|
+
if (!module) {
|
|
165
|
+
this.set(NONE);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.set({
|
|
169
|
+
...this.state,
|
|
170
|
+
supported,
|
|
171
|
+
speakerphone: this.state.current ? this.state.current.type === 'speaker' : speakerChosen ?? true
|
|
172
|
+
});
|
|
173
|
+
const stops = [(0, _native.listen)(module.onAudioDevicesChanged, 'onAudioDevicesChanged', e => this.setOutputs(e.devices.map(_native.toMediaDevice))), (0, _native.listen)(module.onAudioOutputChanged, 'onAudioOutputChanged', e => this.setCurrent(e.device ? (0, _native.toMediaDevice)(e.device) : null))];
|
|
174
|
+
this.stop = () => {
|
|
175
|
+
for (const stop of stops) stop();
|
|
176
|
+
};
|
|
177
|
+
module.listAudioDevices().then(list => this.setOutputs(list.map(_native.toMediaDevice)), err => (0, _errors.logError)('could not list the audio devices', err));
|
|
178
|
+
module.currentAudioOutput().then(device => this.setCurrent(device ? (0, _native.toMediaDevice)(device) : null), err => (0, _errors.logError)('could not read the audio output', err));
|
|
179
|
+
}
|
|
180
|
+
setOutputs(devices) {
|
|
181
|
+
const outputs = this.state.supported ? devices.filter(d => d.kind === 'audiooutput') : [];
|
|
182
|
+
const same = outputs.length === this.state.outputs.length && outputs.every((d, i) => d.deviceId === this.state.outputs[i].deviceId && d.label === this.state.outputs[i].label);
|
|
183
|
+
if (!same) this.set({
|
|
184
|
+
...this.state,
|
|
185
|
+
outputs
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
setCurrent(device) {
|
|
189
|
+
if (device?.deviceId === this.state.current?.deviceId) return;
|
|
190
|
+
this.set({
|
|
191
|
+
...this.state,
|
|
192
|
+
current: device,
|
|
193
|
+
speakerphone: device ? device.type === 'speaker' : speakerChosen ?? true
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
set(next) {
|
|
197
|
+
this.state = next;
|
|
198
|
+
for (const listener of [...this.listeners]) listener();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const outputs = new OutputStore();
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Where call audio plays, and the choice of output (an Android "speaker / phone / headset" picker). Works before any
|
|
205
|
+
* call: a choice made then is kept. On iOS and without the native module `supported` is false and the calls reject.
|
|
206
|
+
*/
|
|
207
|
+
function useAudioOutput() {
|
|
208
|
+
const state = (0, _react.useSyncExternalStore)(outputs.subscribe, outputs.snapshot, outputs.snapshot);
|
|
209
|
+
return (0, _react.useMemo)(() => ({
|
|
210
|
+
...state,
|
|
211
|
+
select: selectAudioOutput,
|
|
212
|
+
setSpeakerphoneOn
|
|
213
|
+
}), [state]);
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=audio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_react","require","_reactNative","_client","_native","_errors","speakerChosen","setSpeakerphoneOn","on","module","requireNative","Boolean","outputs","preferenceChanged","selectAudioOutput","deviceId","routed","err","nativeFailure","RpcError","nativeConstants","audioMode","speakerPreference","provider","AudioHold","holder","users","pendingRelease","constructor","onError","retain","unretain","Math","max","queueMicrotask","release","acquire","speakerphone","nativeModule","audioAcquire","then","id","audioRelease","holds","WeakMap","audioHoldOf","client","hold","get","set","NONE","Object","freeze","supported","current","OutputStore","state","listeners","Set","stop","subscribe","listener","add","size","start","delete","snapshot","type","Platform","OS","stops","listen","onAudioDevicesChanged","e","setOutputs","devices","map","toMediaDevice","onAudioOutputChanged","setCurrent","device","listAudioDevices","list","logError","currentAudioOutput","filter","d","kind","same","length","every","i","label","next","useAudioOutput","useSyncExternalStore","useMemo","select"],"sourceRoot":"../../src","sources":["audio.ts"],"mappings":"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","ignoreList":[]}
|