@fishjam-cloud/react-native-client 0.2.4 → 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/LICENSE +201 -0
- package/README.md +9 -8
- package/android/build.gradle +4 -4
- package/android/src/main/java/io/fishjam/reactnative/EmitableEvents.kt +25 -12
- package/android/src/main/java/io/fishjam/reactnative/Errors.kt +1 -2
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClient.kt +94 -52
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClientModule.kt +42 -90
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewView.kt +14 -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 +16 -10
- package/build/RNFishjamClientModule.d.ts.map +1 -1
- package/build/RNFishjamClientModule.js.map +1 -1
- package/build/common/client.d.ts +3 -3
- package/build/common/client.d.ts.map +1 -1
- package/build/common/client.js +4 -4
- package/build/common/client.js.map +1 -1
- package/build/common/metadata.d.ts +3 -3
- package/build/common/metadata.d.ts.map +1 -1
- package/build/common/metadata.js +4 -4
- package/build/common/metadata.js.map +1 -1
- package/build/common/webRTC.d.ts +0 -8
- package/build/common/webRTC.d.ts.map +1 -1
- package/build/common/webRTC.js +0 -9
- package/build/common/webRTC.js.map +1 -1
- package/build/components/VideoRendererView.d.ts +1 -1
- package/build/components/VideoRendererView.js +1 -1
- package/build/components/VideoRendererView.js.map +1 -1
- package/build/debug/common/webRTC.d.ts +9 -0
- package/build/debug/common/webRTC.d.ts.map +1 -0
- package/build/debug/common/webRTC.js +11 -0
- package/build/debug/common/webRTC.js.map +1 -0
- package/build/debug/index.d.ts +4 -0
- package/build/debug/index.d.ts.map +1 -0
- package/build/debug/index.js +3 -0
- package/build/debug/index.js.map +1 -0
- package/build/debug/stats/types.d.ts.map +1 -0
- package/build/debug/stats/types.js.map +1 -0
- package/build/debug/stats/useRTCStatistics.d.ts.map +1 -0
- package/build/{stats → debug/stats}/useRTCStatistics.js +1 -1
- package/build/debug/stats/useRTCStatistics.js.map +1 -0
- 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 +2 -0
- package/build/hooks/useFishjamEvent.d.ts.map +1 -1
- package/build/hooks/useFishjamEvent.js +2 -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/usePeerStatus.d.ts +19 -0
- package/build/hooks/usePeerStatus.d.ts.map +1 -0
- package/build/hooks/usePeerStatus.js +18 -0
- package/build/hooks/usePeerStatus.js.map +1 -0
- package/build/hooks/{useParticipants.d.ts → usePeers.d.ts} +11 -11
- package/build/hooks/usePeers.d.ts.map +1 -0
- package/build/hooks/usePeers.js +34 -0
- package/build/hooks/usePeers.js.map +1 -0
- 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 +8 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -5
- 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/debug/package.json +4 -0
- package/ios/Events.swift +30 -13
- package/ios/RNFishjamClient.podspec +1 -1
- package/ios/RNFishjamClient.swift +196 -106
- package/ios/RNFishjamClientModule.swift +34 -34
- package/ios/VideoPreviewView.swift +26 -12
- package/ios/VideoRendererView.swift +5 -14
- package/package.json +7 -6
- package/plugin/build/withFishjamAndroid.js +1 -1
- package/plugin/build/withFishjamIos.js +2 -2
- package/build/hooks/useParticipants.d.ts.map +0 -1
- package/build/hooks/useParticipants.js +0 -34
- package/build/hooks/useParticipants.js.map +0 -1
- package/build/stats/types.d.ts.map +0 -1
- package/build/stats/types.js.map +0 -1
- package/build/stats/useRTCStatistics.d.ts.map +0 -1
- package/build/stats/useRTCStatistics.js.map +0 -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/build/{stats → debug/stats}/types.d.ts +0 -0
- /package/build/{stats → debug/stats}/types.js +0 -0
- /package/build/{stats → debug/stats}/useRTCStatistics.d.ts +0 -0
|
@@ -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,37 +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
|
|
99
101
|
|
|
100
102
|
@Field
|
|
101
|
-
val
|
|
103
|
+
val enableMicrophone: Boolean = false
|
|
104
|
+
|
|
105
|
+
@Field
|
|
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(
|
|
110
|
-
"IsCameraOn",
|
|
111
|
-
"IsMicrophoneOn",
|
|
112
|
-
"IsScreenShareOn",
|
|
113
|
-
"SimulcastConfigUpdate",
|
|
114
|
-
"PeersUpdate",
|
|
115
|
-
"AudioDeviceUpdate",
|
|
116
|
-
"SendMediaEvent",
|
|
117
|
-
"BandwidthEstimation",
|
|
118
|
-
"ReconnectionRetriesLimitReached",
|
|
119
|
-
"ReconnectionStarted",
|
|
120
|
-
"Reconnected",
|
|
121
|
-
"Warning"
|
|
122
|
-
)
|
|
112
|
+
val mutex = Mutex()
|
|
123
113
|
|
|
124
114
|
val rnFishjamClient =
|
|
125
115
|
RNFishjamClient { name: String, data: Map<String, Any?> ->
|
|
126
116
|
sendEvent(name, data)
|
|
127
117
|
}
|
|
128
118
|
|
|
129
|
-
|
|
119
|
+
Name("RNFishjamClient")
|
|
120
|
+
|
|
121
|
+
Events(EmitableEvents.allEvents)
|
|
130
122
|
|
|
131
123
|
OnCreate {
|
|
132
124
|
rnFishjamClient.onModuleCreate(appContext)
|
|
@@ -144,11 +136,31 @@ class RNFishjamClientModule : Module() {
|
|
|
144
136
|
rnFishjamClient.onActivityResult(result.requestCode, result.resultCode, result.data)
|
|
145
137
|
}
|
|
146
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
|
+
|
|
147
159
|
AsyncFunction(
|
|
148
160
|
"joinRoom"
|
|
149
|
-
) { url: String,
|
|
161
|
+
) { url: String, peerToken: String, peerMetadata: Map<String, Any>, config: ConnectConfig, promise: Promise ->
|
|
150
162
|
CoroutineScope(Dispatchers.Main).launch {
|
|
151
|
-
rnFishjamClient.joinRoom(url,
|
|
163
|
+
rnFishjamClient.joinRoom(url, peerToken, peerMetadata, config, promise)
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
166
|
|
|
@@ -164,20 +176,12 @@ class RNFishjamClientModule : Module() {
|
|
|
164
176
|
}
|
|
165
177
|
}
|
|
166
178
|
|
|
167
|
-
Property("isMicrophoneOn") {
|
|
168
|
-
return@Property rnFishjamClient.isMicrophoneOn
|
|
169
|
-
}
|
|
170
|
-
|
|
171
179
|
AsyncFunction("toggleMicrophone") Coroutine { ->
|
|
172
180
|
withContext(Dispatchers.Main) {
|
|
173
181
|
rnFishjamClient.toggleMicrophone()
|
|
174
182
|
}
|
|
175
183
|
}
|
|
176
184
|
|
|
177
|
-
Property("isCameraOn") {
|
|
178
|
-
return@Property rnFishjamClient.isCameraOn
|
|
179
|
-
}
|
|
180
|
-
|
|
181
185
|
AsyncFunction("toggleCamera") Coroutine { ->
|
|
182
186
|
withContext(Dispatchers.Main) {
|
|
183
187
|
rnFishjamClient.toggleCamera()
|
|
@@ -212,14 +216,6 @@ class RNFishjamClientModule : Module() {
|
|
|
212
216
|
}
|
|
213
217
|
}
|
|
214
218
|
|
|
215
|
-
Property("cameras") {
|
|
216
|
-
return@Property rnFishjamClient.getCaptureDevices()
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
Property("isScreenShareOn") {
|
|
220
|
-
return@Property rnFishjamClient.isScreenShareOn
|
|
221
|
-
}
|
|
222
|
-
|
|
223
219
|
AsyncFunction("getPeers") Coroutine { ->
|
|
224
220
|
withContext(Dispatchers.Main) {
|
|
225
221
|
rnFishjamClient.getPeers()
|
|
@@ -312,60 +308,16 @@ class RNFishjamClientModule : Module() {
|
|
|
312
308
|
|
|
313
309
|
AsyncFunction("getStatistics") { rnFishjamClient.getStatistics() }
|
|
314
310
|
|
|
315
|
-
Function("
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
val channelId =
|
|
321
|
-
config.channelId
|
|
322
|
-
?: throw CodedException(message = "Missing `channelId` for startForegroundService")
|
|
323
|
-
val channelName =
|
|
324
|
-
config.channelName
|
|
325
|
-
?: throw CodedException(message = "Missing `channelName` for startForegroundService")
|
|
326
|
-
val notificationContent =
|
|
327
|
-
config.notificationContent
|
|
328
|
-
?: throw CodedException(message = "Missing `notificationContent` for startForegroundService")
|
|
329
|
-
val notificationTitle =
|
|
330
|
-
config.notificationTitle
|
|
331
|
-
?: throw CodedException(message = "Missing `notificationTitle` for startForegroundService")
|
|
332
|
-
val foregroundServiceTypes = config.foregroundServiceTypes
|
|
333
|
-
|
|
334
|
-
if (foregroundServiceTypes.isEmpty()) {
|
|
335
|
-
throw CodedException(message = "`foregroundServiceTypes` cannot be empty")
|
|
336
|
-
}
|
|
311
|
+
Function("configureForegroundService") { config: ForegroundServiceNotificationConfig ->
|
|
312
|
+
return@Function rnFishjamClient.configureForegroundService(config)
|
|
313
|
+
}
|
|
337
314
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
appContext.reactContext,
|
|
341
|
-
FishjamForegroundService::class.java
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
serviceIntent.putExtra("channelId", channelId)
|
|
345
|
-
serviceIntent.putExtra("channelName", channelName)
|
|
346
|
-
serviceIntent.putExtra("notificationTitle", notificationContent)
|
|
347
|
-
serviceIntent.putExtra("notificationContent", notificationTitle)
|
|
348
|
-
serviceIntent.putExtra("foregroundServiceTypes", foregroundServiceTypes)
|
|
349
|
-
|
|
350
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
351
|
-
appContext.reactContext!!.startForegroundService(serviceIntent)
|
|
352
|
-
} else {
|
|
353
|
-
appContext.reactContext!!.startService(serviceIntent)
|
|
354
|
-
}
|
|
315
|
+
AsyncFunction("startForegroundService") Coroutine { config: ForegroundServicePermissionsConfig ->
|
|
316
|
+
rnFishjamClient.startForegroundService(config)
|
|
355
317
|
}
|
|
356
318
|
|
|
357
319
|
Function("stopForegroundService") {
|
|
358
|
-
|
|
359
|
-
throw CodedException(message = "reactContext not found")
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
val serviceIntent: Intent =
|
|
363
|
-
Intent(
|
|
364
|
-
appContext.reactContext,
|
|
365
|
-
FishjamForegroundService::class.java
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
appContext.reactContext!!.stopService(serviceIntent)
|
|
320
|
+
rnFishjamClient.stopForegroundService()
|
|
369
321
|
}
|
|
370
322
|
}
|
|
371
323
|
}
|
|
@@ -4,20 +4,25 @@ 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
|
-
) : VideoView(context, appContext)
|
|
12
|
+
) : VideoView(context, appContext),
|
|
13
|
+
LocalCameraTrackChangedListener {
|
|
12
14
|
private var localVideoTrack: LocalVideoTrack? = null
|
|
13
15
|
|
|
14
|
-
private fun
|
|
16
|
+
private fun trySetLocalCameraTrack() {
|
|
15
17
|
localVideoTrack =
|
|
16
18
|
RNFishjamClient.fishjamClient.getLocalEndpoint().tracks.values.firstOrNull { track ->
|
|
17
19
|
track is LocalVideoTrack
|
|
18
20
|
} as? LocalVideoTrack?
|
|
19
|
-
|
|
20
21
|
videoView.let { localVideoTrack?.addRenderer(it) }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private fun initialize() {
|
|
25
|
+
trySetLocalCameraTrack()
|
|
21
26
|
super.setupTrack()
|
|
22
27
|
}
|
|
23
28
|
|
|
@@ -27,14 +32,20 @@ class VideoPreviewView(
|
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
override fun onDetachedFromWindow() {
|
|
35
|
+
RNFishjamClient.localCameraTracksChangedListenersManager.remove(this)
|
|
30
36
|
super.onDetachedFromWindow()
|
|
31
37
|
dispose()
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
override fun onAttachedToWindow() {
|
|
41
|
+
RNFishjamClient.localCameraTracksChangedListenersManager.add(this)
|
|
35
42
|
super.onAttachedToWindow()
|
|
36
43
|
initialize()
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
override fun getVideoTrack(): VideoTrack? = localVideoTrack
|
|
47
|
+
|
|
48
|
+
override fun onLocalCameraTrackChanged() {
|
|
49
|
+
if (localVideoTrack == null) trySetLocalCameraTrack()
|
|
50
|
+
}
|
|
40
51
|
}
|
|
@@ -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")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package io.fishjam.reactnative.managers
|
|
2
|
+
|
|
3
|
+
interface LocalCameraTrackChangedListener {
|
|
4
|
+
fun onLocalCameraTrackChanged()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class LocalCameraTracksChangedListenersManager {
|
|
8
|
+
private var listeners: MutableList<LocalCameraTrackChangedListener> = mutableListOf()
|
|
9
|
+
|
|
10
|
+
fun add(listener: LocalCameraTrackChangedListener) {
|
|
11
|
+
listeners.add(listener)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fun remove(listener: LocalCameraTrackChangedListener) {
|
|
15
|
+
listeners.remove(listener)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fun notifyListeners() {
|
|
19
|
+
for (listener in listeners) listener.onLocalCameraTrackChanged()
|
|
20
|
+
}
|
|
21
|
+
}
|
package/android/src/main/java/io/fishjam/reactnative/managers/LocalTracksSwitchListenersManager.kt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package io.fishjam.reactnative.managers
|
|
2
|
+
|
|
3
|
+
interface LocalTrackSwitchListener {
|
|
4
|
+
suspend fun onLocalTrackWillSwitch()
|
|
5
|
+
|
|
6
|
+
suspend fun onLocalTrackSwitched()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class LocalTracksSwitchListenersManager {
|
|
10
|
+
private val listeners: MutableList<LocalTrackSwitchListener> = mutableListOf()
|
|
11
|
+
|
|
12
|
+
fun add(listener: LocalTrackSwitchListener) {
|
|
13
|
+
listeners.add(listener)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fun remove(listener: LocalTrackSwitchListener) {
|
|
17
|
+
listeners.remove(listener)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
suspend fun notifyWillSwitch() {
|
|
21
|
+
for (listener in listeners) listener.onLocalTrackWillSwitch()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
suspend fun notifySwitched() {
|
|
25
|
+
for (listener in listeners) listener.onLocalTrackSwitched()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package io.fishjam.reactnative.managers
|
|
2
|
+
|
|
3
|
+
interface TrackUpdateListener {
|
|
4
|
+
fun onTracksUpdate()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class TracksUpdateListenersManager {
|
|
8
|
+
private val listeners: MutableList<TrackUpdateListener> = mutableListOf()
|
|
9
|
+
|
|
10
|
+
fun add(listener: TrackUpdateListener) {
|
|
11
|
+
listeners.add(listener)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fun remove(listener: TrackUpdateListener) {
|
|
15
|
+
listeners.remove(listener)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fun notifyListeners() {
|
|
19
|
+
for (listener in listeners) listener.onTracksUpdate()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package io.fishjam.reactnative.utils
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import expo.modules.interfaces.permissions.Permissions
|
|
6
|
+
import expo.modules.interfaces.permissions.PermissionsStatus
|
|
7
|
+
import expo.modules.kotlin.AppContext
|
|
8
|
+
import expo.modules.kotlin.exception.Exceptions
|
|
9
|
+
import kotlin.coroutines.resume
|
|
10
|
+
import kotlin.coroutines.suspendCoroutine
|
|
11
|
+
|
|
12
|
+
object PermissionUtils {
|
|
13
|
+
fun hasCameraPermission(appContext: AppContext?): Boolean = hasPermission(appContext, Manifest.permission.CAMERA)
|
|
14
|
+
|
|
15
|
+
fun hasMicrophonePermission(appContext: AppContext?): Boolean = hasPermission(appContext, Manifest.permission.RECORD_AUDIO)
|
|
16
|
+
|
|
17
|
+
suspend fun requestCameraPermission(appContext: AppContext?): Boolean = requestAccessIfNeeded(appContext, Manifest.permission.CAMERA)
|
|
18
|
+
|
|
19
|
+
suspend fun requestMicrophonePermission(appContext: AppContext?): Boolean =
|
|
20
|
+
requestAccessIfNeeded(appContext, Manifest.permission.RECORD_AUDIO)
|
|
21
|
+
|
|
22
|
+
private suspend fun requestAccessIfNeeded(
|
|
23
|
+
appContext: AppContext?,
|
|
24
|
+
permission: String
|
|
25
|
+
): Boolean {
|
|
26
|
+
val permissionsManager: Permissions =
|
|
27
|
+
appContext?.permissions
|
|
28
|
+
?: throw Exceptions.PermissionsModuleNotFound()
|
|
29
|
+
|
|
30
|
+
if (hasPermission(appContext, permission)) {
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return suspendCoroutine { continuation ->
|
|
35
|
+
permissionsManager.askForPermissions({ result ->
|
|
36
|
+
continuation.resume(result[permission]?.status == PermissionsStatus.GRANTED)
|
|
37
|
+
}, permission)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private fun hasPermission(
|
|
42
|
+
appContext: AppContext?,
|
|
43
|
+
permission: String
|
|
44
|
+
): Boolean = appContext?.reactContext?.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
|
|
45
|
+
}
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
import { EventEmitter } from 'expo-modules-core';
|
|
2
2
|
import { NativeModule } from 'react-native';
|
|
3
|
-
import type { RTCStats } from './stats/types';
|
|
4
|
-
import type {
|
|
3
|
+
import type { RTCStats } from './debug/stats/types';
|
|
4
|
+
import type { SimulcastConfig } from './types';
|
|
5
5
|
import type { CameraConfigInternal, Camera } from './hooks/useCamera';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Peer } from './hooks/usePeers';
|
|
7
7
|
import type { ScreenShareOptionsInternal } from './hooks/useScreenShare';
|
|
8
8
|
import type { ConnectionConfig } from './common/client';
|
|
9
|
+
import { PeerStatus } from './hooks/usePeerStatus';
|
|
10
|
+
import { ForegroundServiceNotificationConfig, ForegroundServicePermissionsConfigInternal } from './hooks/useForegroundService';
|
|
9
11
|
type Metadata = {
|
|
10
12
|
[key: string]: any;
|
|
11
13
|
};
|
|
12
14
|
type RNFishjamClient = {
|
|
13
|
-
|
|
15
|
+
isMicrophoneOn: boolean;
|
|
16
|
+
isCameraOn: boolean;
|
|
17
|
+
isScreenShareOn: boolean;
|
|
18
|
+
isAppScreenShareOn: boolean;
|
|
19
|
+
cameras: ReadonlyArray<Camera>;
|
|
20
|
+
peerStatus: PeerStatus;
|
|
21
|
+
joinRoom: (url: string, peerToken: string, peerMetadata: Metadata, config: ConnectionConfig) => Promise<void>;
|
|
14
22
|
leaveRoom: () => Promise<void>;
|
|
15
23
|
startCamera: (config: CameraConfigInternal) => Promise<void>;
|
|
16
|
-
isMicrophoneOn: boolean;
|
|
17
24
|
toggleMicrophone: () => Promise<boolean>;
|
|
18
|
-
isCameraOn: boolean;
|
|
19
25
|
toggleCamera: () => Promise<boolean>;
|
|
20
26
|
flipCamera: () => Promise<void>;
|
|
21
27
|
switchCamera: (cameraId: string) => Promise<void>;
|
|
22
|
-
cameras: ReadonlyArray<Camera>;
|
|
23
28
|
handleScreenSharePermission: () => Promise<'granted' | 'denied'>;
|
|
24
29
|
toggleScreenShare: (screenShareOptions: Partial<ScreenShareOptionsInternal>) => Promise<void>;
|
|
25
|
-
|
|
26
|
-
getPeers: <PeerMetadataType extends Metadata>() => Promise<
|
|
30
|
+
toggleAppScreenShare: (screenShareOptions: Partial<ScreenShareOptionsInternal>) => Promise<void>;
|
|
31
|
+
getPeers: <PeerMetadataType extends Metadata>() => Promise<Peer<PeerMetadataType>[]>;
|
|
27
32
|
updatePeerMetadata: <MetadataType extends Metadata>(metadata: MetadataType) => Promise<void>;
|
|
28
33
|
updateVideoTrackMetadata: <MetadataType extends Metadata>(metadata: MetadataType) => Promise<void>;
|
|
29
34
|
updateAudioTrackMetadata: <MetadataType extends Metadata>(metadata: MetadataType) => Promise<void>;
|
|
@@ -42,7 +47,8 @@ type RNFishjamClient = {
|
|
|
42
47
|
setVideoTrackBandwidth: (bandwidth: number) => Promise<void>;
|
|
43
48
|
changeWebRTCLoggingSeverity: (severity: string) => Promise<void>;
|
|
44
49
|
getStatistics: () => Promise<RTCStats>;
|
|
45
|
-
|
|
50
|
+
configureForegroundService: (config: ForegroundServiceNotificationConfig) => void;
|
|
51
|
+
startForegroundService: (config: ForegroundServicePermissionsConfigInternal) => Promise<void>;
|
|
46
52
|
stopForegroundService: () => void;
|
|
47
53
|
};
|
|
48
54
|
export declare const nativeModuleEventEmitter: EventEmitter;
|