@fishjam-cloud/react-native-client 0.3.0 → 0.4.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/README.md +2 -2
- package/android/build.gradle +2 -2
- package/android/src/main/java/io/fishjam/reactnative/Errors.kt +1 -2
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClient.kt +52 -27
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClientModule.kt +40 -79
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewView.kt +4 -3
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewViewModule.kt +5 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoRendererView.kt +4 -3
- package/android/src/main/java/io/fishjam/reactnative/VideoRendererViewModule.kt +2 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoView.kt +3 -2
- package/android/src/main/java/io/fishjam/reactnative/{FishjamForegroundService.kt → foregroundService/FishjamForegroundService.kt} +7 -2
- package/android/src/main/java/io/fishjam/reactnative/foregroundService/ForegroundServiceManager.kt +147 -0
- package/android/src/main/java/io/fishjam/reactnative/managers/LocalCameraTracksChangedListenersManager.kt +21 -0
- package/android/src/main/java/io/fishjam/reactnative/managers/LocalTracksSwitchListenersManager.kt +27 -0
- package/android/src/main/java/io/fishjam/reactnative/managers/TracksUpdateListenersManager.kt +21 -0
- package/android/src/main/java/io/fishjam/reactnative/utils/PermissionUtils.kt +45 -0
- package/build/RNFishjamClientModule.d.ts +6 -2
- package/build/RNFishjamClientModule.d.ts.map +1 -1
- package/build/RNFishjamClientModule.js.map +1 -1
- package/build/hooks/useAppScreenShare.d.ts +11 -0
- package/build/hooks/useAppScreenShare.d.ts.map +1 -0
- package/build/hooks/useAppScreenShare.js +54 -0
- package/build/hooks/useAppScreenShare.js.map +1 -0
- package/build/hooks/useFishjamEvent.d.ts +1 -0
- package/build/hooks/useFishjamEvent.d.ts.map +1 -1
- package/build/hooks/useFishjamEvent.js +1 -0
- package/build/hooks/useFishjamEvent.js.map +1 -1
- package/build/hooks/useForegroundService.d.ts +44 -0
- package/build/hooks/useForegroundService.d.ts.map +1 -0
- package/build/hooks/useForegroundService.js +57 -0
- package/build/hooks/useForegroundService.js.map +1 -0
- package/build/hooks/useMicrophone.d.ts +1 -1
- package/build/hooks/useMicrophone.js +1 -1
- package/build/hooks/useMicrophone.js.map +1 -1
- package/build/hooks/useScreenShare.d.ts +1 -0
- package/build/hooks/useScreenShare.d.ts.map +1 -1
- package/build/hooks/useScreenShare.js +20 -13
- package/build/hooks/useScreenShare.js.map +1 -1
- package/build/index.d.ts +4 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/types.d.ts +0 -25
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -10
- package/build/types.js.map +1 -1
- package/ios/Events.swift +1 -0
- package/ios/RNFishjamClient.podspec +1 -1
- package/ios/RNFishjamClient.swift +144 -57
- package/ios/RNFishjamClientModule.swift +32 -24
- package/ios/VideoPreviewView.swift +6 -10
- package/ios/VideoRendererView.swift +5 -14
- package/package.json +6 -6
- package/plugin/build/withFishjamAndroid.js +1 -1
- package/build/utils/foregroundService.d.ts +0 -32
- package/build/utils/foregroundService.d.ts.map +0 -1
- package/build/utils/foregroundService.js +0 -43
- package/build/utils/foregroundService.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
# Installation
|
|
8
8
|
|
|
9
|
-
Check out the [installation](https://fishjam
|
|
9
|
+
Check out the [installation](https://docs.fishjam.io/react-native/installation) section of our docs for the detailed installation instructions.
|
|
10
10
|
|
|
11
11
|
# Documentation
|
|
12
12
|
|
|
13
|
-
Check out our [documentation](https://fishjam
|
|
13
|
+
Check out our [documentation](https://docs.fishjam.io/) on how Fishjam Cloud work and how to integrate our service with your app.
|
|
14
14
|
|
|
15
15
|
There is also [API documentation](https://fishjam-cloud.github.io/mobile-client-sdk/modules/_fishjam_cloud_react_native_client.html) available.
|
|
16
16
|
|
package/android/build.gradle
CHANGED
|
@@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
|
|
|
2
2
|
apply plugin: 'kotlin-android'
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
|
-
def packageVersion = '0.
|
|
5
|
+
def packageVersion = '0.4.0'
|
|
6
6
|
|
|
7
7
|
group = 'io.fishjam.reactnative'
|
|
8
8
|
version = "${packageVersion}"
|
|
@@ -86,6 +86,6 @@ dependencies {
|
|
|
86
86
|
// update version number when releasing
|
|
87
87
|
implementation "com.github.fishjam-cloud:mobile-client-sdk:${packageVersion}"
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
implementation 'com.twilio:audioswitch:1.2.0'
|
|
90
90
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
91
91
|
}
|
|
@@ -11,8 +11,7 @@ class ConnectionError(
|
|
|
11
11
|
reason: AuthError
|
|
12
12
|
) : CodedException(message = "Connection error: ${reason.error}")
|
|
13
13
|
|
|
14
|
-
class MissingScreenSharePermission :
|
|
15
|
-
CodedException(message = "No permission to start screen share, call handleScreenSharePermission first.")
|
|
14
|
+
class MissingScreenSharePermission : CodedException(message = "No permission to start screen share.")
|
|
16
15
|
|
|
17
16
|
class ClientNotConnectedError : CodedException(message = "Client not connected to server yet. Make sure to call connect() first!")
|
|
18
17
|
|
|
@@ -27,6 +27,11 @@ import com.twilio.audioswitch.AudioDevice
|
|
|
27
27
|
import expo.modules.kotlin.AppContext
|
|
28
28
|
import expo.modules.kotlin.Promise
|
|
29
29
|
import expo.modules.kotlin.exception.CodedException
|
|
30
|
+
import io.fishjam.reactnative.foregroundService.ForegroundServiceManager
|
|
31
|
+
import io.fishjam.reactnative.managers.LocalCameraTracksChangedListenersManager
|
|
32
|
+
import io.fishjam.reactnative.managers.LocalTracksSwitchListenersManager
|
|
33
|
+
import io.fishjam.reactnative.managers.TracksUpdateListenersManager
|
|
34
|
+
import io.fishjam.reactnative.utils.PermissionUtils
|
|
30
35
|
import kotlinx.coroutines.CoroutineScope
|
|
31
36
|
import kotlinx.coroutines.Dispatchers
|
|
32
37
|
import kotlinx.coroutines.SupervisorJob
|
|
@@ -70,24 +75,15 @@ class RNFishjamClient(
|
|
|
70
75
|
emitEvent(event, mapOf(event.name to value))
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
fun onTracksUpdate()
|
|
75
|
-
}
|
|
78
|
+
private var foregroundServiceManager: ForegroundServiceManager? = null
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
companion object {
|
|
81
|
+
val trackUpdateListenersManager = TracksUpdateListenersManager()
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
}
|
|
83
|
+
val localTracksSwitchListenerManager = LocalTracksSwitchListenersManager()
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
fun onLocalCameraTrackChanged()
|
|
85
|
-
}
|
|
85
|
+
val localCameraTracksChangedListenersManager = LocalCameraTracksChangedListenersManager()
|
|
86
86
|
|
|
87
|
-
companion object {
|
|
88
|
-
var onTracksUpdateListeners: MutableList<OnTrackUpdateListener> = mutableListOf()
|
|
89
|
-
val onLocalTrackSwitchListener: MutableList<OnLocalTrackSwitchListener> = mutableListOf()
|
|
90
|
-
var localCameraTrackListeners: MutableList<OnLocalCameraTrackChangedListener> = mutableListOf()
|
|
91
87
|
lateinit var fishjamClient: FishjamClient
|
|
92
88
|
|
|
93
89
|
fun getAllPeers(): List<Peer> {
|
|
@@ -253,12 +249,12 @@ class RNFishjamClient(
|
|
|
253
249
|
) {
|
|
254
250
|
peerStatus = PeerStatus.connecting
|
|
255
251
|
connectPromise = promise
|
|
256
|
-
localUserMetadata = peerMetadata
|
|
252
|
+
localUserMetadata = mapOf("server" to emptyMap(), "peer" to peerMetadata)
|
|
257
253
|
fishjamClient.connect(
|
|
258
254
|
com.fishjamcloud.client.ConnectConfig(
|
|
259
255
|
url,
|
|
260
256
|
peerToken,
|
|
261
|
-
|
|
257
|
+
localUserMetadata,
|
|
262
258
|
ReconnectConfig(
|
|
263
259
|
config.reconnectConfig.maxAttempts,
|
|
264
260
|
config.reconnectConfig.initialDelayMs,
|
|
@@ -287,6 +283,11 @@ class RNFishjamClient(
|
|
|
287
283
|
emitWarning("Camera already started. You may only call startCamera once before leaveRoom is called.")
|
|
288
284
|
return
|
|
289
285
|
}
|
|
286
|
+
if (!PermissionUtils.requestCameraPermission(appContext)) {
|
|
287
|
+
emitWarning("Camera permission not granted.")
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
290
291
|
val cameraTrack = createCameraTrack(config)
|
|
291
292
|
setCameraTrackState(cameraTrack, config.cameraEnabled)
|
|
292
293
|
emitEndpoints()
|
|
@@ -311,7 +312,7 @@ class RNFishjamClient(
|
|
|
311
312
|
isCameraOn = isEnabled
|
|
312
313
|
val event = EmitableEvents.IsCameraOn
|
|
313
314
|
emitEvent(event, mapOf(event.name to isEnabled))
|
|
314
|
-
|
|
315
|
+
localCameraTracksChangedListenersManager.notifyListeners()
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
fun toggleCamera(): Boolean {
|
|
@@ -322,21 +323,24 @@ class RNFishjamClient(
|
|
|
322
323
|
|
|
323
324
|
suspend fun flipCamera() {
|
|
324
325
|
ensureVideoTrack()
|
|
325
|
-
|
|
326
|
+
localTracksSwitchListenerManager.notifyWillSwitch()
|
|
326
327
|
getLocalVideoTrack()?.flipCamera()
|
|
327
|
-
|
|
328
|
+
localTracksSwitchListenerManager.notifySwitched()
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
suspend fun switchCamera(cameraId: String) {
|
|
331
332
|
ensureVideoTrack()
|
|
332
|
-
|
|
333
|
+
localTracksSwitchListenerManager.notifyWillSwitch()
|
|
333
334
|
getLocalVideoTrack()?.switchCamera(cameraId)
|
|
334
|
-
|
|
335
|
-
it.onLocalTrackSwitched()
|
|
336
|
-
}
|
|
335
|
+
localTracksSwitchListenerManager.notifySwitched()
|
|
337
336
|
}
|
|
338
337
|
|
|
339
338
|
private suspend fun startMicrophone() {
|
|
339
|
+
if (!PermissionUtils.requestMicrophonePermission(appContext)) {
|
|
340
|
+
emitWarning("Microphone permission not granted.")
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
340
344
|
val microphoneTrack = fishjamClient.createAudioTrack(emptyMap())
|
|
341
345
|
setMicrophoneTrackState(microphoneTrack, true)
|
|
342
346
|
emitEndpoints()
|
|
@@ -376,6 +380,24 @@ class RNFishjamClient(
|
|
|
376
380
|
}
|
|
377
381
|
}
|
|
378
382
|
|
|
383
|
+
fun configureForegroundService(config: ForegroundServiceNotificationConfig) {
|
|
384
|
+
if (foregroundServiceManager == null) {
|
|
385
|
+
foregroundServiceManager = ForegroundServiceManager(appContext!!, config)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
suspend fun startForegroundService(config: ForegroundServicePermissionsConfig) {
|
|
390
|
+
if (foregroundServiceManager == null) {
|
|
391
|
+
throw CodedException("Foreground service not configured.")
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
foregroundServiceManager?.startForegroundService(config)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
fun stopForegroundService() {
|
|
398
|
+
foregroundServiceManager?.stopForegroundService()
|
|
399
|
+
}
|
|
400
|
+
|
|
379
401
|
suspend fun toggleScreenShare(screenShareOptions: ScreenShareOptions) {
|
|
380
402
|
this.screenShareMetadata = screenShareOptions.screenShareMetadata
|
|
381
403
|
this.screenShareQuality = screenShareOptions.quality
|
|
@@ -549,7 +571,8 @@ class RNFishjamClient(
|
|
|
549
571
|
fun toggleScreenShareTrackEncoding(encoding: String): Map<String, Any> {
|
|
550
572
|
ensureScreenShareTrack()
|
|
551
573
|
getLocalScreenShareTrack()?.let {
|
|
552
|
-
screenShareSimulcastConfig =
|
|
574
|
+
screenShareSimulcastConfig =
|
|
575
|
+
toggleTrackEncoding(encoding, it.id(), screenShareSimulcastConfig)
|
|
553
576
|
}
|
|
554
577
|
return getSimulcastConfigAsRNMap(screenShareSimulcastConfig)
|
|
555
578
|
}
|
|
@@ -743,10 +766,12 @@ class RNFishjamClient(
|
|
|
743
766
|
event: EmitableEvents,
|
|
744
767
|
data: Map<String, Any?> = mapOf()
|
|
745
768
|
) {
|
|
746
|
-
|
|
769
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
770
|
+
sendEvent(event.name, data)
|
|
771
|
+
}
|
|
747
772
|
}
|
|
748
773
|
|
|
749
|
-
|
|
774
|
+
fun emitWarning(warning: String) {
|
|
750
775
|
emitEvent(EmitableEvents.Warning, mapOf("message" to warning))
|
|
751
776
|
}
|
|
752
777
|
|
|
@@ -822,7 +847,7 @@ class RNFishjamClient(
|
|
|
822
847
|
|
|
823
848
|
private fun addOrUpdateTrack(track: Track) {
|
|
824
849
|
emitEndpoints()
|
|
825
|
-
|
|
850
|
+
trackUpdateListenersManager.notifyListeners()
|
|
826
851
|
}
|
|
827
852
|
|
|
828
853
|
override fun onTrackReady(track: Track) {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
package io.fishjam.reactnative
|
|
2
2
|
|
|
3
|
-
import android.content.Intent
|
|
4
|
-
import android.os.Build
|
|
5
3
|
import expo.modules.kotlin.Promise
|
|
6
|
-
import expo.modules.kotlin.exception.CodedException
|
|
7
4
|
import expo.modules.kotlin.functions.Coroutine
|
|
8
5
|
import expo.modules.kotlin.modules.Module
|
|
9
6
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
@@ -84,7 +81,7 @@ class ConnectConfig : Record {
|
|
|
84
81
|
val reconnectConfig: ReconnectConfig = ReconnectConfig()
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
class
|
|
84
|
+
class ForegroundServiceNotificationConfig : Record {
|
|
88
85
|
@Field
|
|
89
86
|
val channelId: String? = null
|
|
90
87
|
|
|
@@ -96,24 +93,32 @@ class ForegroundServiceConfig : Record {
|
|
|
96
93
|
|
|
97
94
|
@Field
|
|
98
95
|
val notificationTitle: String? = null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class ForegroundServicePermissionsConfig : Record {
|
|
99
|
+
@Field
|
|
100
|
+
val enableCamera: Boolean = false
|
|
101
|
+
|
|
102
|
+
@Field
|
|
103
|
+
val enableMicrophone: Boolean = false
|
|
99
104
|
|
|
100
105
|
@Field
|
|
101
|
-
val
|
|
106
|
+
val enableScreenSharing: Boolean = false
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
class RNFishjamClientModule : Module() {
|
|
105
110
|
override fun definition() =
|
|
106
111
|
ModuleDefinition {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Events(EmitableEvents.allEvents)
|
|
112
|
+
val mutex = Mutex()
|
|
110
113
|
|
|
111
114
|
val rnFishjamClient =
|
|
112
115
|
RNFishjamClient { name: String, data: Map<String, Any?> ->
|
|
113
116
|
sendEvent(name, data)
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
|
|
119
|
+
Name("RNFishjamClient")
|
|
120
|
+
|
|
121
|
+
Events(EmitableEvents.allEvents)
|
|
117
122
|
|
|
118
123
|
OnCreate {
|
|
119
124
|
rnFishjamClient.onModuleCreate(appContext)
|
|
@@ -131,6 +136,26 @@ class RNFishjamClientModule : Module() {
|
|
|
131
136
|
rnFishjamClient.onActivityResult(result.requestCode, result.resultCode, result.data)
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
Property("isCameraOn") {
|
|
140
|
+
return@Property rnFishjamClient.isCameraOn
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Property("isMicrophoneOn") {
|
|
144
|
+
return@Property rnFishjamClient.isMicrophoneOn
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
Property("cameras") {
|
|
148
|
+
return@Property rnFishjamClient.getCaptureDevices()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
Property("isScreenShareOn") {
|
|
152
|
+
return@Property rnFishjamClient.isScreenShareOn
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Property("peerStatus") {
|
|
156
|
+
return@Property rnFishjamClient.peerStatus
|
|
157
|
+
}
|
|
158
|
+
|
|
134
159
|
AsyncFunction(
|
|
135
160
|
"joinRoom"
|
|
136
161
|
) { url: String, peerToken: String, peerMetadata: Map<String, Any>, config: ConnectConfig, promise: Promise ->
|
|
@@ -151,20 +176,12 @@ class RNFishjamClientModule : Module() {
|
|
|
151
176
|
}
|
|
152
177
|
}
|
|
153
178
|
|
|
154
|
-
Property("isMicrophoneOn") {
|
|
155
|
-
return@Property rnFishjamClient.isMicrophoneOn
|
|
156
|
-
}
|
|
157
|
-
|
|
158
179
|
AsyncFunction("toggleMicrophone") Coroutine { ->
|
|
159
180
|
withContext(Dispatchers.Main) {
|
|
160
181
|
rnFishjamClient.toggleMicrophone()
|
|
161
182
|
}
|
|
162
183
|
}
|
|
163
184
|
|
|
164
|
-
Property("isCameraOn") {
|
|
165
|
-
return@Property rnFishjamClient.isCameraOn
|
|
166
|
-
}
|
|
167
|
-
|
|
168
185
|
AsyncFunction("toggleCamera") Coroutine { ->
|
|
169
186
|
withContext(Dispatchers.Main) {
|
|
170
187
|
rnFishjamClient.toggleCamera()
|
|
@@ -199,14 +216,6 @@ class RNFishjamClientModule : Module() {
|
|
|
199
216
|
}
|
|
200
217
|
}
|
|
201
218
|
|
|
202
|
-
Property("cameras") {
|
|
203
|
-
return@Property rnFishjamClient.getCaptureDevices()
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
Property("isScreenShareOn") {
|
|
207
|
-
return@Property rnFishjamClient.isScreenShareOn
|
|
208
|
-
}
|
|
209
|
-
|
|
210
219
|
AsyncFunction("getPeers") Coroutine { ->
|
|
211
220
|
withContext(Dispatchers.Main) {
|
|
212
221
|
rnFishjamClient.getPeers()
|
|
@@ -299,64 +308,16 @@ class RNFishjamClientModule : Module() {
|
|
|
299
308
|
|
|
300
309
|
AsyncFunction("getStatistics") { rnFishjamClient.getStatistics() }
|
|
301
310
|
|
|
302
|
-
Function("
|
|
303
|
-
|
|
304
|
-
throw CodedException(message = "reactContext not found")
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
val channelId =
|
|
308
|
-
config.channelId
|
|
309
|
-
?: throw CodedException(message = "Missing `channelId` for startForegroundService")
|
|
310
|
-
val channelName =
|
|
311
|
-
config.channelName
|
|
312
|
-
?: throw CodedException(message = "Missing `channelName` for startForegroundService")
|
|
313
|
-
val notificationContent =
|
|
314
|
-
config.notificationContent
|
|
315
|
-
?: throw CodedException(message = "Missing `notificationContent` for startForegroundService")
|
|
316
|
-
val notificationTitle =
|
|
317
|
-
config.notificationTitle
|
|
318
|
-
?: throw CodedException(message = "Missing `notificationTitle` for startForegroundService")
|
|
319
|
-
val foregroundServiceTypes = config.foregroundServiceTypes
|
|
320
|
-
|
|
321
|
-
if (foregroundServiceTypes.isEmpty()) {
|
|
322
|
-
throw CodedException(message = "`foregroundServiceTypes` cannot be empty")
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
val serviceIntent =
|
|
326
|
-
Intent(
|
|
327
|
-
appContext.reactContext,
|
|
328
|
-
FishjamForegroundService::class.java
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
serviceIntent.putExtra("channelId", channelId)
|
|
332
|
-
serviceIntent.putExtra("channelName", channelName)
|
|
333
|
-
serviceIntent.putExtra("notificationTitle", notificationContent)
|
|
334
|
-
serviceIntent.putExtra("notificationContent", notificationTitle)
|
|
335
|
-
serviceIntent.putExtra("foregroundServiceTypes", foregroundServiceTypes)
|
|
336
|
-
|
|
337
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
338
|
-
appContext.reactContext!!.startForegroundService(serviceIntent)
|
|
339
|
-
} else {
|
|
340
|
-
appContext.reactContext!!.startService(serviceIntent)
|
|
341
|
-
}
|
|
311
|
+
Function("configureForegroundService") { config: ForegroundServiceNotificationConfig ->
|
|
312
|
+
return@Function rnFishjamClient.configureForegroundService(config)
|
|
342
313
|
}
|
|
343
314
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
throw CodedException(message = "reactContext not found")
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
val serviceIntent: Intent =
|
|
350
|
-
Intent(
|
|
351
|
-
appContext.reactContext,
|
|
352
|
-
FishjamForegroundService::class.java
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
appContext.reactContext!!.stopService(serviceIntent)
|
|
315
|
+
AsyncFunction("startForegroundService") Coroutine { config: ForegroundServicePermissionsConfig ->
|
|
316
|
+
rnFishjamClient.startForegroundService(config)
|
|
356
317
|
}
|
|
357
318
|
|
|
358
|
-
|
|
359
|
-
|
|
319
|
+
Function("stopForegroundService") {
|
|
320
|
+
rnFishjamClient.stopForegroundService()
|
|
360
321
|
}
|
|
361
322
|
}
|
|
362
323
|
}
|
|
@@ -4,12 +4,13 @@ import android.content.Context
|
|
|
4
4
|
import com.fishjamcloud.client.media.LocalVideoTrack
|
|
5
5
|
import com.fishjamcloud.client.media.VideoTrack
|
|
6
6
|
import expo.modules.kotlin.AppContext
|
|
7
|
+
import io.fishjam.reactnative.managers.LocalCameraTrackChangedListener
|
|
7
8
|
|
|
8
9
|
class VideoPreviewView(
|
|
9
10
|
context: Context,
|
|
10
11
|
appContext: AppContext
|
|
11
12
|
) : VideoView(context, appContext),
|
|
12
|
-
|
|
13
|
+
LocalCameraTrackChangedListener {
|
|
13
14
|
private var localVideoTrack: LocalVideoTrack? = null
|
|
14
15
|
|
|
15
16
|
private fun trySetLocalCameraTrack() {
|
|
@@ -31,13 +32,13 @@ class VideoPreviewView(
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
override fun onDetachedFromWindow() {
|
|
34
|
-
RNFishjamClient.
|
|
35
|
+
RNFishjamClient.localCameraTracksChangedListenersManager.remove(this)
|
|
35
36
|
super.onDetachedFromWindow()
|
|
36
37
|
dispose()
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
override fun onAttachedToWindow() {
|
|
40
|
-
RNFishjamClient.
|
|
41
|
+
RNFishjamClient.localCameraTracksChangedListenersManager.add(this)
|
|
41
42
|
super.onAttachedToWindow()
|
|
42
43
|
initialize()
|
|
43
44
|
}
|
|
@@ -9,6 +9,11 @@ class VideoPreviewViewModule : Module() {
|
|
|
9
9
|
Name("VideoPreviewViewModule")
|
|
10
10
|
|
|
11
11
|
View(VideoPreviewView::class) {
|
|
12
|
+
OnViewDestroys { view: VideoPreviewView ->
|
|
13
|
+
view.dispose()
|
|
14
|
+
RNFishjamClient.localTracksSwitchListenerManager.remove(view)
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
Prop("videoLayout") { view: VideoPreviewView, videoLayout: String ->
|
|
13
18
|
view.setVideoLayout(videoLayout)
|
|
14
19
|
}
|
|
@@ -3,6 +3,7 @@ package io.fishjam.reactnative
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import com.fishjamcloud.client.media.VideoTrack
|
|
5
5
|
import expo.modules.kotlin.AppContext
|
|
6
|
+
import io.fishjam.reactnative.managers.TrackUpdateListener
|
|
6
7
|
import kotlinx.coroutines.CoroutineScope
|
|
7
8
|
import kotlinx.coroutines.Dispatchers
|
|
8
9
|
import kotlinx.coroutines.launch
|
|
@@ -11,12 +12,12 @@ class VideoRendererView(
|
|
|
11
12
|
context: Context,
|
|
12
13
|
appContext: AppContext
|
|
13
14
|
) : VideoView(context, appContext),
|
|
14
|
-
|
|
15
|
+
TrackUpdateListener {
|
|
15
16
|
private var activeVideoTrack: VideoTrack? = null
|
|
16
17
|
private var trackId: String? = null
|
|
17
18
|
|
|
18
19
|
init {
|
|
19
|
-
RNFishjamClient.
|
|
20
|
+
RNFishjamClient.trackUpdateListenersManager.add(this)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
private fun setupTrack(videoTrack: VideoTrack) {
|
|
@@ -46,7 +47,7 @@ class VideoRendererView(
|
|
|
46
47
|
|
|
47
48
|
override fun dispose() {
|
|
48
49
|
activeVideoTrack?.removeRenderer(videoView)
|
|
49
|
-
RNFishjamClient.
|
|
50
|
+
RNFishjamClient.trackUpdateListenersManager.add(this)
|
|
50
51
|
super.dispose()
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -11,6 +11,8 @@ class VideoRendererViewModule : Module() {
|
|
|
11
11
|
View(VideoRendererView::class) {
|
|
12
12
|
OnViewDestroys { view: VideoRendererView ->
|
|
13
13
|
view.dispose()
|
|
14
|
+
RNFishjamClient.trackUpdateListenersManager.remove(view)
|
|
15
|
+
RNFishjamClient.localTracksSwitchListenerManager.remove(view)
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
Prop("trackId") { view: VideoRendererView, trackId: String ->
|
|
@@ -9,6 +9,7 @@ import com.fishjamcloud.client.media.LocalVideoTrack
|
|
|
9
9
|
import com.fishjamcloud.client.media.VideoTrack
|
|
10
10
|
import expo.modules.kotlin.AppContext
|
|
11
11
|
import expo.modules.kotlin.views.ExpoView
|
|
12
|
+
import io.fishjam.reactnative.managers.LocalTrackSwitchListener
|
|
12
13
|
import kotlinx.coroutines.CoroutineScope
|
|
13
14
|
import kotlinx.coroutines.Dispatchers
|
|
14
15
|
import kotlinx.coroutines.delay
|
|
@@ -18,7 +19,7 @@ abstract class VideoView(
|
|
|
18
19
|
context: Context,
|
|
19
20
|
appContext: AppContext
|
|
20
21
|
) : ExpoView(context, appContext),
|
|
21
|
-
|
|
22
|
+
LocalTrackSwitchListener {
|
|
22
23
|
protected val videoView =
|
|
23
24
|
RNFishjamClient.fishjamClient.createVideoViewRenderer().also {
|
|
24
25
|
addView(it)
|
|
@@ -37,7 +38,7 @@ abstract class VideoView(
|
|
|
37
38
|
val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
|
|
38
39
|
|
|
39
40
|
init {
|
|
40
|
-
RNFishjamClient.
|
|
41
|
+
RNFishjamClient.localTracksSwitchListenerManager.add(this)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
fun setVideoLayout(videoLayout: String) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package io.fishjam.reactnative
|
|
1
|
+
package io.fishjam.reactnative.foregroundService
|
|
2
2
|
|
|
3
3
|
import android.app.Notification
|
|
4
4
|
import android.app.NotificationChannel
|
|
@@ -6,6 +6,7 @@ import android.app.NotificationManager
|
|
|
6
6
|
import android.app.PendingIntent
|
|
7
7
|
import android.app.Service
|
|
8
8
|
import android.content.Intent
|
|
9
|
+
import android.os.Binder
|
|
9
10
|
import android.os.Build
|
|
10
11
|
import android.os.IBinder
|
|
11
12
|
import androidx.core.app.NotificationCompat
|
|
@@ -15,7 +16,11 @@ class FishjamForegroundService : Service() {
|
|
|
15
16
|
private const val FOREGROUND_SERVICE_ID = 1668
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
private val binder = LocalBinder()
|
|
20
|
+
|
|
21
|
+
inner class LocalBinder : Binder()
|
|
22
|
+
|
|
23
|
+
override fun onBind(intent: Intent): IBinder = binder
|
|
19
24
|
|
|
20
25
|
override fun onStartCommand(
|
|
21
26
|
intent: Intent?,
|
package/android/src/main/java/io/fishjam/reactnative/foregroundService/ForegroundServiceManager.kt
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package io.fishjam.reactnative.foregroundService
|
|
2
|
+
|
|
3
|
+
import android.content.ComponentName
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.ServiceConnection
|
|
7
|
+
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
|
|
8
|
+
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
|
|
9
|
+
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.os.IBinder
|
|
12
|
+
import expo.modules.kotlin.AppContext
|
|
13
|
+
import expo.modules.kotlin.exception.CodedException
|
|
14
|
+
import io.fishjam.reactnative.ForegroundServiceNotificationConfig
|
|
15
|
+
import io.fishjam.reactnative.ForegroundServicePermissionsConfig
|
|
16
|
+
import io.fishjam.reactnative.utils.PermissionUtils
|
|
17
|
+
import kotlinx.coroutines.CancellableContinuation
|
|
18
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
19
|
+
import kotlin.coroutines.resume
|
|
20
|
+
|
|
21
|
+
class ForegroundServiceManager(
|
|
22
|
+
private val appContext: AppContext,
|
|
23
|
+
notificationConfig: ForegroundServiceNotificationConfig
|
|
24
|
+
) {
|
|
25
|
+
private val reactContext by lazy {
|
|
26
|
+
appContext.reactContext ?: throw CodedException("reactContext not found")
|
|
27
|
+
}
|
|
28
|
+
private var isServiceBound: Boolean = false
|
|
29
|
+
private var serviceConnectedContinuation: CancellableContinuation<Unit>? = null
|
|
30
|
+
|
|
31
|
+
private val serviceIntent =
|
|
32
|
+
Intent(
|
|
33
|
+
appContext.reactContext,
|
|
34
|
+
FishjamForegroundService::class.java
|
|
35
|
+
).apply {
|
|
36
|
+
putExtra(
|
|
37
|
+
"channelId",
|
|
38
|
+
notificationConfig.channelId.orThrow("channelId")
|
|
39
|
+
)
|
|
40
|
+
putExtra(
|
|
41
|
+
"channelName",
|
|
42
|
+
notificationConfig.channelName.orThrow("channelName")
|
|
43
|
+
)
|
|
44
|
+
putExtra(
|
|
45
|
+
"notificationContent",
|
|
46
|
+
notificationConfig.notificationContent.orThrow("notificationContent")
|
|
47
|
+
)
|
|
48
|
+
putExtra(
|
|
49
|
+
"notificationTitle",
|
|
50
|
+
notificationConfig.notificationTitle.orThrow("notificationTitle")
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private val connection =
|
|
55
|
+
object : ServiceConnection {
|
|
56
|
+
override fun onServiceConnected(
|
|
57
|
+
className: ComponentName,
|
|
58
|
+
service: IBinder
|
|
59
|
+
) {
|
|
60
|
+
isServiceBound = true
|
|
61
|
+
serviceConnectedContinuation?.resume(Unit)
|
|
62
|
+
serviceConnectedContinuation = null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun onServiceDisconnected(arg0: ComponentName) {
|
|
66
|
+
isServiceBound = false
|
|
67
|
+
serviceConnectedContinuation?.cancel()
|
|
68
|
+
serviceConnectedContinuation = null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
suspend fun startForegroundService(permissionsConfig: ForegroundServicePermissionsConfig) {
|
|
73
|
+
val foregroundServiceTypes = buildForegroundServiceTypes(permissionsConfig)
|
|
74
|
+
|
|
75
|
+
if (foregroundServiceTypes.isEmpty()) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
serviceIntent.putExtra("foregroundServiceTypes", foregroundServiceTypes.toIntArray())
|
|
80
|
+
|
|
81
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
82
|
+
reactContext.startForegroundService(serviceIntent)
|
|
83
|
+
} else {
|
|
84
|
+
reactContext.startService(serviceIntent)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
bindServiceIfNeededAndAwait()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fun stopForegroundService() {
|
|
91
|
+
if (isServiceBound) {
|
|
92
|
+
appContext.currentActivity?.unbindService(connection)
|
|
93
|
+
?: throw CodedException("Current activity not found")
|
|
94
|
+
isServiceBound = false
|
|
95
|
+
}
|
|
96
|
+
reactContext.stopService(serviceIntent)
|
|
97
|
+
serviceConnectedContinuation?.cancel()
|
|
98
|
+
serviceConnectedContinuation = null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private fun buildForegroundServiceTypes(permissionsConfig: ForegroundServicePermissionsConfig): List<Int> =
|
|
102
|
+
buildList {
|
|
103
|
+
if (permissionsConfig.enableCamera) {
|
|
104
|
+
addIfPermissionGranted(
|
|
105
|
+
FOREGROUND_SERVICE_TYPE_CAMERA,
|
|
106
|
+
"camera",
|
|
107
|
+
PermissionUtils::hasCameraPermission
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
if (permissionsConfig.enableMicrophone) {
|
|
111
|
+
addIfPermissionGranted(
|
|
112
|
+
FOREGROUND_SERVICE_TYPE_MICROPHONE,
|
|
113
|
+
"microphone",
|
|
114
|
+
PermissionUtils::hasMicrophonePermission
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
if (permissionsConfig.enableScreenSharing) add(FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun MutableList<Int>.addIfPermissionGranted(
|
|
121
|
+
serviceType: Int,
|
|
122
|
+
permissionName: String,
|
|
123
|
+
hasPermission: (AppContext) -> Boolean
|
|
124
|
+
) {
|
|
125
|
+
if (!hasPermission(appContext)) {
|
|
126
|
+
throw CodedException("Cannot start a $permissionName foreground service without $permissionName permission.")
|
|
127
|
+
}
|
|
128
|
+
add(serviceType)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private suspend fun bindServiceIfNeededAndAwait() =
|
|
132
|
+
suspendCancellableCoroutine { continuation ->
|
|
133
|
+
if (!isServiceBound) {
|
|
134
|
+
serviceConnectedContinuation = continuation
|
|
135
|
+
runCatching {
|
|
136
|
+
appContext.currentActivity?.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)
|
|
137
|
+
}.onFailure { error ->
|
|
138
|
+
continuation.cancel(CodedException("Failed to bind service: ${error.message}"))
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
continuation.resume(Unit)
|
|
142
|
+
serviceConnectedContinuation = null
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private fun String?.orThrow(fieldName: String): String = this ?: throw CodedException("Missing `$fieldName` for startForegroundService")
|