@fishjam-cloud/react-native-client 0.2.0 → 0.2.2
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 +21 -8
- package/android/build.gradle +5 -3
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/fishjam/reactnative/AudioDeviceKind.kt +34 -0
- package/android/src/main/java/io/fishjam/reactnative/AudioSwitchManager.kt +61 -0
- package/android/src/main/java/io/fishjam/reactnative/EmitableEvents.kt +14 -0
- package/android/src/main/java/io/fishjam/reactnative/Errors.kt +31 -0
- package/android/src/main/java/io/fishjam/reactnative/FishjamForegroundService.kt +90 -0
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClient.kt +870 -0
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClientModule.kt +370 -0
- package/android/src/main/java/io/fishjam/reactnative/Utils.kt +11 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewView.kt +40 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewViewModule.kt +17 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoRendererView.kt +58 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoRendererViewModule.kt +25 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoView.kt +77 -0
- package/expo-module.config.json +17 -0
- package/ios/RNFishjamClient.podspec +30 -0
- package/ios/RNFishjamClientModule.swift +2 -2
- package/package.json +10 -7
- package/plugin/build/types.d.ts +9 -0
- package/plugin/build/types.js +2 -0
- package/plugin/build/withFishjam.d.ts +4 -0
- package/plugin/build/withFishjam.js +13 -0
- package/plugin/build/withFishjamAndroid.d.ts +3 -0
- package/plugin/build/withFishjamAndroid.js +32 -0
- package/plugin/build/withFishjamIos.d.ts +9 -0
- package/plugin/build/withFishjamIos.js +222 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import expo.modules.kotlin.Promise
|
|
6
|
+
import expo.modules.kotlin.exception.CodedException
|
|
7
|
+
import expo.modules.kotlin.functions.Coroutine
|
|
8
|
+
import expo.modules.kotlin.modules.Module
|
|
9
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
10
|
+
import expo.modules.kotlin.records.Field
|
|
11
|
+
import expo.modules.kotlin.records.Record
|
|
12
|
+
import kotlinx.coroutines.CoroutineScope
|
|
13
|
+
import kotlinx.coroutines.Dispatchers
|
|
14
|
+
import kotlinx.coroutines.launch
|
|
15
|
+
import kotlinx.coroutines.sync.Mutex
|
|
16
|
+
import kotlinx.coroutines.sync.withLock
|
|
17
|
+
import kotlinx.coroutines.withContext
|
|
18
|
+
|
|
19
|
+
class SimulcastConfig : Record {
|
|
20
|
+
@Field
|
|
21
|
+
val enabled: Boolean = false
|
|
22
|
+
|
|
23
|
+
@Field
|
|
24
|
+
val activeEncodings: List<String> = emptyList()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class CameraConfig : Record {
|
|
28
|
+
@Field
|
|
29
|
+
val quality: String = "VGA169"
|
|
30
|
+
|
|
31
|
+
@Field
|
|
32
|
+
val flipVideo: Boolean = false
|
|
33
|
+
|
|
34
|
+
@Field
|
|
35
|
+
val videoTrackMetadata: Map<String, Any> = emptyMap()
|
|
36
|
+
|
|
37
|
+
@Field
|
|
38
|
+
val simulcastConfig: SimulcastConfig = SimulcastConfig()
|
|
39
|
+
|
|
40
|
+
// expo-modules on Android don't support Either type
|
|
41
|
+
@Field
|
|
42
|
+
val maxBandwidthMap: Map<String, Int> = emptyMap()
|
|
43
|
+
|
|
44
|
+
@Field
|
|
45
|
+
val maxBandwidthInt: Int = 0
|
|
46
|
+
|
|
47
|
+
@Field
|
|
48
|
+
val cameraEnabled: Boolean = true
|
|
49
|
+
|
|
50
|
+
@Field
|
|
51
|
+
val cameraId: String? = null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class ScreencastOptions : Record {
|
|
55
|
+
@Field
|
|
56
|
+
val quality: String = "HD15"
|
|
57
|
+
|
|
58
|
+
@Field
|
|
59
|
+
val screencastMetadata: Map<String, Any> = emptyMap()
|
|
60
|
+
|
|
61
|
+
@Field
|
|
62
|
+
val simulcastConfig: SimulcastConfig = SimulcastConfig()
|
|
63
|
+
|
|
64
|
+
@Field
|
|
65
|
+
val maxBandwidthMap: Map<String, Int> = emptyMap()
|
|
66
|
+
|
|
67
|
+
@Field
|
|
68
|
+
val maxBandwidthInt: Int = 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class ReconnectConfig : Record {
|
|
72
|
+
@Field
|
|
73
|
+
val maxAttempts: Int = 5
|
|
74
|
+
|
|
75
|
+
@Field
|
|
76
|
+
val initialDelayMs: Long = 1000
|
|
77
|
+
|
|
78
|
+
@Field
|
|
79
|
+
val delayMs: Long = 1000
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class ConnectConfig : Record {
|
|
83
|
+
@Field
|
|
84
|
+
val reconnectConfig: ReconnectConfig = ReconnectConfig()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class ForegroundServiceConfig : Record {
|
|
88
|
+
@Field
|
|
89
|
+
val channelId: String? = null
|
|
90
|
+
|
|
91
|
+
@Field
|
|
92
|
+
val channelName: String? = null
|
|
93
|
+
|
|
94
|
+
@Field
|
|
95
|
+
val notificationContent: String? = null
|
|
96
|
+
|
|
97
|
+
@Field
|
|
98
|
+
val notificationTitle: String? = null
|
|
99
|
+
|
|
100
|
+
@Field
|
|
101
|
+
val foregroundServiceTypes: IntArray = intArrayOf()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class RNFishjamClientModule : Module() {
|
|
105
|
+
override fun definition() =
|
|
106
|
+
ModuleDefinition {
|
|
107
|
+
Name("RNFishjamClient")
|
|
108
|
+
|
|
109
|
+
Events(
|
|
110
|
+
"IsCameraOn",
|
|
111
|
+
"IsMicrophoneOn",
|
|
112
|
+
"IsScreencastOn",
|
|
113
|
+
"SimulcastConfigUpdate",
|
|
114
|
+
"PeersUpdate",
|
|
115
|
+
"AudioDeviceUpdate",
|
|
116
|
+
"SendMediaEvent",
|
|
117
|
+
"BandwidthEstimation",
|
|
118
|
+
"ReconnectionRetriesLimitReached",
|
|
119
|
+
"ReconnectionStarted",
|
|
120
|
+
"Reconnected"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
val rnFishjamClient =
|
|
124
|
+
RNFishjamClient { name: String, data: Map<String, Any?> ->
|
|
125
|
+
sendEvent(name, data)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
val mutex = Mutex()
|
|
129
|
+
|
|
130
|
+
OnCreate {
|
|
131
|
+
rnFishjamClient.onModuleCreate(appContext)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
OnDestroy {
|
|
135
|
+
rnFishjamClient.onModuleDestroy()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
OnActivityDestroys {
|
|
139
|
+
rnFishjamClient.leaveRoom()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
OnActivityResult { _, result ->
|
|
143
|
+
rnFishjamClient.onActivityResult(result.requestCode, result.resultCode, result.data)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
AsyncFunction(
|
|
147
|
+
"joinRoom"
|
|
148
|
+
) { url: String, participantToken: String, participantMetadata: Map<String, Any>, config: ConnectConfig, promise: Promise ->
|
|
149
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
150
|
+
rnFishjamClient.joinRoom(url, participantToken, participantMetadata, config, promise)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
AsyncFunction("leaveRoom") Coroutine { ->
|
|
155
|
+
withContext(Dispatchers.Main) {
|
|
156
|
+
rnFishjamClient.leaveRoom()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
AsyncFunction("startCamera") Coroutine { config: CameraConfig ->
|
|
161
|
+
withContext(Dispatchers.Main) {
|
|
162
|
+
rnFishjamClient.startCamera(config)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
Property("isMicrophoneOn") {
|
|
167
|
+
return@Property rnFishjamClient.isMicrophoneOn
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
AsyncFunction("toggleMicrophone") Coroutine { ->
|
|
171
|
+
withContext(Dispatchers.Main) {
|
|
172
|
+
rnFishjamClient.toggleMicrophone()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Property("isCameraOn") {
|
|
177
|
+
return@Property rnFishjamClient.isCameraOn
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
AsyncFunction("toggleCamera") Coroutine { ->
|
|
181
|
+
withContext(Dispatchers.Main) {
|
|
182
|
+
rnFishjamClient.toggleCamera()
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
AsyncFunction("flipCamera") Coroutine { ->
|
|
187
|
+
mutex.withLock {
|
|
188
|
+
withContext(Dispatchers.Main) {
|
|
189
|
+
rnFishjamClient.flipCamera()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
AsyncFunction("switchCamera") Coroutine { cameraId: String ->
|
|
195
|
+
mutex.withLock {
|
|
196
|
+
withContext(Dispatchers.Main) {
|
|
197
|
+
rnFishjamClient.switchCamera(cameraId)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
AsyncFunction("handleScreencastPermission") { promise: Promise ->
|
|
203
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
204
|
+
rnFishjamClient.handleScreencastPermission(promise)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
AsyncFunction("toggleScreencast") Coroutine { screencastOptions: ScreencastOptions ->
|
|
209
|
+
withContext(Dispatchers.Main) {
|
|
210
|
+
rnFishjamClient.toggleScreencast(screencastOptions)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
Property("cameras") {
|
|
215
|
+
return@Property rnFishjamClient.getCaptureDevices()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
Property("isScreencastOn") {
|
|
219
|
+
return@Property rnFishjamClient.isScreencastOn
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
AsyncFunction("getPeers") Coroutine { ->
|
|
223
|
+
withContext(Dispatchers.Main) {
|
|
224
|
+
rnFishjamClient.getPeers()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
AsyncFunction("updatePeerMetadata") Coroutine { metadata: Map<String, Any> ->
|
|
229
|
+
withContext(Dispatchers.Main) {
|
|
230
|
+
rnFishjamClient.updatePeerMetadata(metadata)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
AsyncFunction("updateVideoTrackMetadata") Coroutine { metadata: Map<String, Any> ->
|
|
235
|
+
withContext(Dispatchers.Main) {
|
|
236
|
+
rnFishjamClient.updateLocalVideoTrackMetadata(metadata)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
AsyncFunction("updateAudioTrackMetadata") Coroutine { metadata: Map<String, Any> ->
|
|
241
|
+
withContext(Dispatchers.Main) {
|
|
242
|
+
rnFishjamClient.updateLocalAudioTrackMetadata(metadata)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
AsyncFunction("updateScreencastTrackMetadata") Coroutine { metadata: Map<String, Any> ->
|
|
247
|
+
withContext(Dispatchers.Main) {
|
|
248
|
+
rnFishjamClient.updateLocalScreencastTrackMetadata(metadata)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
AsyncFunction("setOutputAudioDevice") { audioDevice: String ->
|
|
253
|
+
rnFishjamClient.setOutputAudioDevice(audioDevice)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
AsyncFunction("startAudioSwitcher") {
|
|
257
|
+
rnFishjamClient.startAudioSwitcher()
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
AsyncFunction("stopAudioSwitcher") {
|
|
261
|
+
rnFishjamClient.stopAudioSwitcher()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
AsyncFunction("toggleScreencastTrackEncoding") Coroutine { encoding: String ->
|
|
265
|
+
withContext(Dispatchers.Main) {
|
|
266
|
+
rnFishjamClient.toggleScreencastTrackEncoding(encoding)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
AsyncFunction("setScreencastTrackBandwidth") Coroutine { bandwidth: Int ->
|
|
271
|
+
withContext(Dispatchers.Main) {
|
|
272
|
+
rnFishjamClient.setScreencastTrackBandwidth(bandwidth)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
AsyncFunction("setScreencastTrackEncodingBandwidth") Coroutine { encoding: String, bandwidth: Int ->
|
|
277
|
+
withContext(Dispatchers.Main) {
|
|
278
|
+
rnFishjamClient.setScreencastTrackEncodingBandwidth(encoding, bandwidth)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
AsyncFunction("setTargetTrackEncoding") Coroutine { trackId: String, encoding: String ->
|
|
283
|
+
withContext(Dispatchers.Main) {
|
|
284
|
+
rnFishjamClient.setTargetTrackEncoding(trackId, encoding)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
AsyncFunction("toggleVideoTrackEncoding") Coroutine { encoding: String ->
|
|
289
|
+
withContext(Dispatchers.Main) {
|
|
290
|
+
rnFishjamClient.toggleVideoTrackEncoding(encoding)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
AsyncFunction("setVideoTrackEncodingBandwidth") Coroutine { encoding: String, bandwidth: Int ->
|
|
295
|
+
withContext(Dispatchers.Main) {
|
|
296
|
+
rnFishjamClient.setVideoTrackEncodingBandwidth(encoding, bandwidth)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
AsyncFunction("setVideoTrackBandwidth") Coroutine { bandwidth: Int ->
|
|
301
|
+
withContext(Dispatchers.Main) {
|
|
302
|
+
rnFishjamClient.setVideoTrackBandwidth(bandwidth)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
AsyncFunction("changeWebRTCLoggingSeverity") Coroutine { severity: String ->
|
|
307
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
308
|
+
rnFishjamClient.changeWebRTCLoggingSeverity(severity)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
AsyncFunction("getStatistics") { rnFishjamClient.getStatistics() }
|
|
313
|
+
|
|
314
|
+
Function("startForegroundService") { config: ForegroundServiceConfig ->
|
|
315
|
+
if (appContext.reactContext == null) {
|
|
316
|
+
throw CodedException(message = "reactContext not found")
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
val channelId =
|
|
320
|
+
config.channelId
|
|
321
|
+
?: throw CodedException(message = "Missing `channelId` for startForegroundService")
|
|
322
|
+
val channelName =
|
|
323
|
+
config.channelName
|
|
324
|
+
?: throw CodedException(message = "Missing `channelName` for startForegroundService")
|
|
325
|
+
val notificationContent =
|
|
326
|
+
config.notificationContent
|
|
327
|
+
?: throw CodedException(message = "Missing `notificationContent` for startForegroundService")
|
|
328
|
+
val notificationTitle =
|
|
329
|
+
config.notificationTitle
|
|
330
|
+
?: throw CodedException(message = "Missing `notificationTitle` for startForegroundService")
|
|
331
|
+
val foregroundServiceTypes = config.foregroundServiceTypes
|
|
332
|
+
|
|
333
|
+
if (foregroundServiceTypes.isEmpty()) {
|
|
334
|
+
throw CodedException(message = "`foregroundServiceTypes` cannot be empty")
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
val serviceIntent =
|
|
338
|
+
Intent(
|
|
339
|
+
appContext.reactContext,
|
|
340
|
+
FishjamForegroundService::class.java
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
serviceIntent.putExtra("channelId", channelId)
|
|
344
|
+
serviceIntent.putExtra("channelName", channelName)
|
|
345
|
+
serviceIntent.putExtra("notificationTitle", notificationContent)
|
|
346
|
+
serviceIntent.putExtra("notificationContent", notificationTitle)
|
|
347
|
+
serviceIntent.putExtra("foregroundServiceTypes", foregroundServiceTypes)
|
|
348
|
+
|
|
349
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
350
|
+
appContext.reactContext!!.startForegroundService(serviceIntent)
|
|
351
|
+
} else {
|
|
352
|
+
appContext.reactContext!!.startService(serviceIntent)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
Function("stopForegroundService") {
|
|
357
|
+
if (appContext.reactContext == null) {
|
|
358
|
+
throw CodedException(message = "reactContext not found")
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
val serviceIntent: Intent =
|
|
362
|
+
Intent(
|
|
363
|
+
appContext.reactContext,
|
|
364
|
+
FishjamForegroundService::class.java
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
appContext.reactContext!!.stopService(serviceIntent)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import com.fishjamcloud.client.models.TrackEncoding
|
|
4
|
+
|
|
5
|
+
internal fun String.toTrackEncoding(): TrackEncoding =
|
|
6
|
+
when (this) {
|
|
7
|
+
"l" -> TrackEncoding.L
|
|
8
|
+
"m" -> TrackEncoding.M
|
|
9
|
+
"h" -> TrackEncoding.H
|
|
10
|
+
else -> throw IllegalArgumentException("Invalid encoding specified: $this")
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.fishjamcloud.client.media.LocalVideoTrack
|
|
5
|
+
import com.fishjamcloud.client.media.VideoTrack
|
|
6
|
+
import expo.modules.kotlin.AppContext
|
|
7
|
+
|
|
8
|
+
class VideoPreviewView(
|
|
9
|
+
context: Context,
|
|
10
|
+
appContext: AppContext
|
|
11
|
+
) : VideoView(context, appContext) {
|
|
12
|
+
private var localVideoTrack: LocalVideoTrack? = null
|
|
13
|
+
|
|
14
|
+
private fun initialize() {
|
|
15
|
+
localVideoTrack =
|
|
16
|
+
RNFishjamClient.fishjamClient.getLocalEndpoint().tracks.values.firstOrNull { track ->
|
|
17
|
+
track is LocalVideoTrack
|
|
18
|
+
} as? LocalVideoTrack?
|
|
19
|
+
|
|
20
|
+
videoView.let { localVideoTrack?.addRenderer(it) }
|
|
21
|
+
super.setupTrack()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun dispose() {
|
|
25
|
+
videoView.let { localVideoTrack?.removeRenderer(it) }
|
|
26
|
+
super.dispose()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun onDetachedFromWindow() {
|
|
30
|
+
super.onDetachedFromWindow()
|
|
31
|
+
dispose()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun onAttachedToWindow() {
|
|
35
|
+
super.onAttachedToWindow()
|
|
36
|
+
initialize()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override fun getVideoTrack(): VideoTrack? = localVideoTrack
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.modules.Module
|
|
4
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
|
+
|
|
6
|
+
class VideoPreviewViewModule : Module() {
|
|
7
|
+
override fun definition() =
|
|
8
|
+
ModuleDefinition {
|
|
9
|
+
Name("VideoPreviewViewModule")
|
|
10
|
+
|
|
11
|
+
View(VideoPreviewView::class) {
|
|
12
|
+
Prop("videoLayout") { view: VideoPreviewView, videoLayout: String ->
|
|
13
|
+
view.setVideoLayout(videoLayout)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.fishjamcloud.client.media.VideoTrack
|
|
5
|
+
import expo.modules.kotlin.AppContext
|
|
6
|
+
import kotlinx.coroutines.CoroutineScope
|
|
7
|
+
import kotlinx.coroutines.Dispatchers
|
|
8
|
+
import kotlinx.coroutines.launch
|
|
9
|
+
|
|
10
|
+
class VideoRendererView(
|
|
11
|
+
context: Context,
|
|
12
|
+
appContext: AppContext
|
|
13
|
+
) : VideoView(context, appContext),
|
|
14
|
+
RNFishjamClient.OnTrackUpdateListener {
|
|
15
|
+
private var activeVideoTrack: VideoTrack? = null
|
|
16
|
+
private var trackId: String? = null
|
|
17
|
+
|
|
18
|
+
init {
|
|
19
|
+
RNFishjamClient.onTracksUpdateListeners.add(this)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private fun setupTrack(videoTrack: VideoTrack) {
|
|
23
|
+
if (activeVideoTrack == videoTrack) return
|
|
24
|
+
|
|
25
|
+
activeVideoTrack?.removeRenderer(videoView)
|
|
26
|
+
activeVideoTrack = videoTrack
|
|
27
|
+
|
|
28
|
+
videoTrack.addRenderer(videoView)
|
|
29
|
+
|
|
30
|
+
super.setupTrack()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private fun update() {
|
|
34
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
35
|
+
val peers = RNFishjamClient.getAllPeers()
|
|
36
|
+
val endpoint = peers.firstOrNull { it.tracks[trackId] != null } ?: return@launch
|
|
37
|
+
val videoTrack = endpoint.tracks[trackId] as? VideoTrack ?: return@launch
|
|
38
|
+
setupTrack(videoTrack)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun init(trackId: String) {
|
|
43
|
+
this.trackId = trackId
|
|
44
|
+
update()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override fun dispose() {
|
|
48
|
+
activeVideoTrack?.removeRenderer(videoView)
|
|
49
|
+
RNFishjamClient.onTracksUpdateListeners.remove(this)
|
|
50
|
+
super.dispose()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun onTracksUpdate() {
|
|
54
|
+
update()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override fun getVideoTrack(): VideoTrack? = activeVideoTrack
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.modules.Module
|
|
4
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
|
+
|
|
6
|
+
class VideoRendererViewModule : Module() {
|
|
7
|
+
override fun definition() =
|
|
8
|
+
ModuleDefinition {
|
|
9
|
+
Name("VideoRendererViewModule")
|
|
10
|
+
|
|
11
|
+
View(VideoRendererView::class) {
|
|
12
|
+
OnViewDestroys { view: VideoRendererView ->
|
|
13
|
+
view.dispose()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Prop("trackId") { view: VideoRendererView, trackId: String ->
|
|
17
|
+
view.init(trackId)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Prop("videoLayout") { view: VideoRendererView, videoLayout: String ->
|
|
21
|
+
view.setVideoLayout(videoLayout)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import android.animation.ValueAnimator
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.drawable.ColorDrawable
|
|
7
|
+
import android.view.animation.LinearInterpolator
|
|
8
|
+
import com.fishjamcloud.client.media.LocalVideoTrack
|
|
9
|
+
import com.fishjamcloud.client.media.VideoTrack
|
|
10
|
+
import expo.modules.kotlin.AppContext
|
|
11
|
+
import expo.modules.kotlin.views.ExpoView
|
|
12
|
+
import kotlinx.coroutines.CoroutineScope
|
|
13
|
+
import kotlinx.coroutines.Dispatchers
|
|
14
|
+
import kotlinx.coroutines.delay
|
|
15
|
+
import org.webrtc.RendererCommon
|
|
16
|
+
|
|
17
|
+
abstract class VideoView(
|
|
18
|
+
context: Context,
|
|
19
|
+
appContext: AppContext
|
|
20
|
+
) : ExpoView(context, appContext),
|
|
21
|
+
RNFishjamClient.OnLocalTrackSwitchListener {
|
|
22
|
+
protected val videoView =
|
|
23
|
+
RNFishjamClient.fishjamClient.createVideoViewRenderer().also {
|
|
24
|
+
addView(it)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private val fadeAnimation: ValueAnimator =
|
|
28
|
+
ValueAnimator.ofArgb(Color.TRANSPARENT, Color.BLACK).apply {
|
|
29
|
+
duration = 200
|
|
30
|
+
interpolator = LinearInterpolator()
|
|
31
|
+
addUpdateListener {
|
|
32
|
+
val colorValue = it.animatedValue as Int
|
|
33
|
+
foreground = ColorDrawable(colorValue)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
|
|
38
|
+
|
|
39
|
+
init {
|
|
40
|
+
RNFishjamClient.onLocalTrackSwitchListener.add(this)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun setVideoLayout(videoLayout: String) {
|
|
44
|
+
val scalingType =
|
|
45
|
+
when (videoLayout) {
|
|
46
|
+
"FILL" -> RendererCommon.ScalingType.SCALE_ASPECT_FILL
|
|
47
|
+
"FIT" -> RendererCommon.ScalingType.SCALE_ASPECT_FIT
|
|
48
|
+
else -> RendererCommon.ScalingType.SCALE_ASPECT_FILL
|
|
49
|
+
}
|
|
50
|
+
videoView.setScalingType(scalingType)
|
|
51
|
+
videoView.setEnableHardwareScaler(true)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
open fun dispose() {
|
|
55
|
+
videoView.release()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected abstract fun getVideoTrack(): VideoTrack?
|
|
59
|
+
|
|
60
|
+
override suspend fun onLocalTrackWillSwitch() {
|
|
61
|
+
if (getVideoTrack() is LocalVideoTrack) {
|
|
62
|
+
fadeAnimation.start()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override suspend fun onLocalTrackSwitched() {
|
|
67
|
+
if (getVideoTrack() is LocalVideoTrack) {
|
|
68
|
+
videoView.setMirror((getVideoTrack() as? LocalVideoTrack)?.isFrontCamera() ?: false)
|
|
69
|
+
delay(500)
|
|
70
|
+
fadeAnimation.reverse()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun setupTrack() {
|
|
75
|
+
videoView.setMirror((getVideoTrack() as? LocalVideoTrack)?.isFrontCamera() ?: false)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"platforms": ["ios", "android", "web"],
|
|
3
|
+
"ios": {
|
|
4
|
+
"modules": [
|
|
5
|
+
"RNFishjamClientModule",
|
|
6
|
+
"VideoPreviewViewModule",
|
|
7
|
+
"VideoRendererViewModule"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"android": {
|
|
11
|
+
"modules": [
|
|
12
|
+
"io.fishjam.reactnative.RNFishjamClientModule",
|
|
13
|
+
"io.fishjam.reactnative.VideoPreviewViewModule",
|
|
14
|
+
"io.fishjam.reactnative.VideoRendererViewModule"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'RNFishjamClient'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platform = :ios, '13.0'
|
|
14
|
+
s.swift_version = '5.4'
|
|
15
|
+
s.source = { git: 'https://github.com/fishjam-cloud/mobile-client-sdk/tree/main/packages/react-native-client/ios' }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
|
|
18
|
+
s.dependency 'ExpoModulesCore'
|
|
19
|
+
|
|
20
|
+
# Swift/Objective-C compatibility
|
|
21
|
+
s.pod_target_xcconfig = {
|
|
22
|
+
'DEFINES_MODULE' => 'YES',
|
|
23
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
s.source_files = '**/*.{h,m,swift}'
|
|
27
|
+
|
|
28
|
+
s.dependency 'FishjamCloudClient', '>= 0.1'
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -97,7 +97,7 @@ public class RNFishjamClientModule: Module {
|
|
|
97
97
|
return client
|
|
98
98
|
}()
|
|
99
99
|
|
|
100
|
-
AsyncFunction("
|
|
100
|
+
AsyncFunction("joinRoom") {
|
|
101
101
|
(
|
|
102
102
|
url: String, participantToken: String, participantMetadata: [String: Any], config: ConnectConfig,
|
|
103
103
|
promise: Promise
|
|
@@ -136,7 +136,7 @@ public class RNFishjamClientModule: Module {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
AsyncFunction("switchCamera") { (cameraId: String) in
|
|
139
|
-
try rnFishjamClient.switchCamera(
|
|
139
|
+
try rnFishjamClient.switchCamera(cameraId: cameraId)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
Property("cameras") {
|