@fishjam-cloud/react-native-client 0.4.1 → 0.5.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.
Files changed (87) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/io/fishjam/reactnative/EmitableEvents.kt +99 -14
  3. package/android/src/main/java/io/fishjam/reactnative/RNFishjamClient.kt +63 -92
  4. package/android/src/main/java/io/fishjam/reactnative/RNFishjamClientModule.kt +12 -23
  5. package/android/src/main/java/io/fishjam/reactnative/VideoRendererViewModule.kt +6 -2
  6. package/android/src/main/java/io/fishjam/reactnative/VideoView.kt +41 -3
  7. package/android/src/main/java/io/fishjam/reactnative/extensions/CaptureDevice.kt +17 -0
  8. package/android/src/main/java/io/fishjam/reactnative/foregroundService/FishjamForegroundService.kt +10 -4
  9. package/android/src/main/java/io/fishjam/reactnative/foregroundService/ForegroundServiceManager.kt +41 -70
  10. package/android/src/main/java/io/fishjam/reactnative/foregroundService/ForegroundServiceState.kt +46 -0
  11. package/android/src/main/java/io/fishjam/reactnative/managers/ListenerManager.kt +22 -0
  12. package/android/src/main/java/io/fishjam/reactnative/managers/LocalCameraTracksChangedListenersManager.kt +2 -15
  13. package/android/src/main/java/io/fishjam/reactnative/managers/LocalTracksSwitchListenersManager.kt +1 -11
  14. package/android/src/main/java/io/fishjam/reactnative/managers/TracksUpdateListenersManager.kt +1 -15
  15. package/build/RNFishjamClientModule.d.ts +6 -7
  16. package/build/RNFishjamClientModule.d.ts.map +1 -1
  17. package/build/RNFishjamClientModule.js.map +1 -1
  18. package/build/common/client.d.ts +2 -1
  19. package/build/common/client.d.ts.map +1 -1
  20. package/build/common/client.js.map +1 -1
  21. package/build/common/webRTC.d.ts +2 -1
  22. package/build/common/webRTC.d.ts.map +1 -1
  23. package/build/common/webRTC.js.map +1 -1
  24. package/build/components/FishjamRoom/GridTrackItem.d.ts +11 -0
  25. package/build/components/FishjamRoom/GridTrackItem.d.ts.map +1 -0
  26. package/build/components/FishjamRoom/GridTrackItem.js +56 -0
  27. package/build/components/FishjamRoom/GridTrackItem.js.map +1 -0
  28. package/build/components/FishjamRoom/VideosGrid.d.ts +3 -0
  29. package/build/components/FishjamRoom/VideosGrid.d.ts.map +1 -0
  30. package/build/components/FishjamRoom/VideosGrid.js +20 -0
  31. package/build/components/FishjamRoom/VideosGrid.js.map +1 -0
  32. package/build/components/FishjamRoom/index.d.ts +6 -0
  33. package/build/components/FishjamRoom/index.d.ts.map +1 -0
  34. package/build/components/FishjamRoom/index.js +29 -0
  35. package/build/components/FishjamRoom/index.js.map +1 -0
  36. package/build/components/FishjamRoom/parsePeersToTracks.d.ts +4 -0
  37. package/build/components/FishjamRoom/parsePeersToTracks.d.ts.map +1 -0
  38. package/build/components/FishjamRoom/parsePeersToTracks.js +36 -0
  39. package/build/components/FishjamRoom/parsePeersToTracks.js.map +1 -0
  40. package/build/components/VideoRendererView.d.ts +6 -1
  41. package/build/components/VideoRendererView.d.ts.map +1 -1
  42. package/build/components/VideoRendererView.js.map +1 -1
  43. package/build/hooks/useAppScreenShare.js +1 -1
  44. package/build/hooks/useAppScreenShare.js.map +1 -1
  45. package/build/hooks/useCamera.d.ts +6 -2
  46. package/build/hooks/useCamera.d.ts.map +1 -1
  47. package/build/hooks/useCamera.js +14 -17
  48. package/build/hooks/useCamera.js.map +1 -1
  49. package/build/hooks/useFishjamEvent.d.ts +1 -1
  50. package/build/hooks/useFishjamEvent.js +1 -1
  51. package/build/hooks/useFishjamEvent.js.map +1 -1
  52. package/build/hooks/useFishjamEventState.d.ts +3 -0
  53. package/build/hooks/useFishjamEventState.d.ts.map +1 -0
  54. package/build/hooks/useFishjamEventState.js +16 -0
  55. package/build/hooks/useFishjamEventState.js.map +1 -0
  56. package/build/hooks/useForegroundService.d.ts +12 -20
  57. package/build/hooks/useForegroundService.d.ts.map +1 -1
  58. package/build/hooks/useForegroundService.js +14 -4
  59. package/build/hooks/useForegroundService.js.map +1 -1
  60. package/build/hooks/useMicrophone.d.ts.map +1 -1
  61. package/build/hooks/useMicrophone.js +5 -10
  62. package/build/hooks/useMicrophone.js.map +1 -1
  63. package/build/hooks/usePeers.d.ts +50 -11
  64. package/build/hooks/usePeers.d.ts.map +1 -1
  65. package/build/hooks/usePeers.js +41 -6
  66. package/build/hooks/usePeers.js.map +1 -1
  67. package/build/hooks/useScreenShare.d.ts.map +1 -1
  68. package/build/hooks/useScreenShare.js +5 -6
  69. package/build/hooks/useScreenShare.js.map +1 -1
  70. package/build/index.d.ts +3 -2
  71. package/build/index.d.ts.map +1 -1
  72. package/build/index.js +1 -0
  73. package/build/index.js.map +1 -1
  74. package/build/types.d.ts +1 -1
  75. package/build/types.d.ts.map +1 -1
  76. package/build/types.js.map +1 -1
  77. package/build/utils/errorListener.js +2 -2
  78. package/build/utils/errorListener.js.map +1 -1
  79. package/ios/Events.swift +97 -19
  80. package/ios/RNFishjamClient.podspec +1 -1
  81. package/ios/RNFishjamClient.swift +74 -109
  82. package/ios/RNFishjamClientModule.swift +6 -6
  83. package/ios/VideoPreviewView.swift +13 -13
  84. package/ios/VideoRendererView.swift +28 -25
  85. package/ios/VideoRendererViewModule.swift +8 -4
  86. package/ios/extensions/AVCaptureDevice.swift +20 -0
  87. package/package.json +8 -10
@@ -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.4.1'
5
+ def packageVersion = '0.5.0'
6
6
 
7
7
  group = 'io.fishjam.reactnative'
8
8
  version = "${packageVersion}"
@@ -1,5 +1,9 @@
1
1
  package io.fishjam.reactnative
2
2
 
3
+ import com.fishjamcloud.client.models.SimulcastConfig
4
+ import com.twilio.audioswitch.AudioDevice
5
+ import io.fishjam.reactnative.extensions.LocalCamera
6
+
3
7
  @Suppress("ktlint:standard:enum-entry-name-case")
4
8
  enum class PeerStatus {
5
9
  connecting,
@@ -8,21 +12,102 @@ enum class PeerStatus {
8
12
  idle
9
13
  }
10
14
 
11
- enum class EmitableEvents {
12
- IsCameraOn,
13
- IsMicrophoneOn,
14
- IsScreenShareOn,
15
- SimulcastConfigUpdate,
16
- PeersUpdate,
17
- AudioDeviceUpdate,
18
- BandwidthEstimation,
19
- ReconnectionRetriesLimitReached,
20
- ReconnectionStarted,
21
- Reconnected,
22
- Warning,
23
- PeerStatusChanged;
15
+ class EmitableEvent private constructor(
16
+ private val event: EventName,
17
+ private val eventContent: Any? = null
18
+ ) {
19
+ enum class EventName {
20
+ IsMicrophoneOn,
21
+ IsScreenShareOn,
22
+ SimulcastConfigUpdate,
23
+ PeersUpdate,
24
+ AudioDeviceUpdate,
25
+ BandwidthEstimation,
26
+ ReconnectionRetriesLimitReached,
27
+ ReconnectionStarted,
28
+ Reconnected,
29
+ Warning,
30
+ PeerStatusChanged,
31
+ CurrentCameraChanged
32
+ }
33
+
34
+ val name: String
35
+ get() = event.name
36
+
37
+ val data: Map<String, Any?>
38
+ get() = mapOf(event.name to eventContent)
24
39
 
25
40
  companion object {
26
- val allEvents = EmitableEvents.entries.map { it.name }.toTypedArray()
41
+ val reconnectionRetriesLimitReached = EmitableEvent(EventName.ReconnectionRetriesLimitReached)
42
+ val reconnectionStarted = EmitableEvent(EventName.ReconnectionStarted)
43
+ val reconnected = EmitableEvent(EventName.Reconnected)
44
+
45
+ fun isMicrophoneOn(enabled: Boolean) = EmitableEvent(EventName.IsMicrophoneOn, enabled)
46
+
47
+ fun isScreenShareOn(enabled: Boolean) = EmitableEvent(EventName.IsScreenShareOn, enabled)
48
+
49
+ fun bandwidthEstimation(estimation: Long) = EmitableEvent(EventName.BandwidthEstimation, estimation.toFloat())
50
+
51
+ fun warning(message: String) = EmitableEvent(EventName.Warning, message)
52
+
53
+ fun peerStatusChanged(peerStatus: PeerStatus) = EmitableEvent(EventName.PeerStatusChanged, peerStatus.name)
54
+
55
+ fun currentCameraChanged(
56
+ localCamera: LocalCamera?,
57
+ isCameraOn: Boolean
58
+ ) = EmitableEvent(
59
+ EventName.CurrentCameraChanged,
60
+ mapOf(
61
+ "currentCamera" to localCamera,
62
+ "isCameraOn" to isCameraOn
63
+ )
64
+ )
65
+
66
+ fun simulcastConfigUpdate(simulcastConfig: SimulcastConfig) =
67
+ EmitableEvent(
68
+ EventName.SimulcastConfigUpdate,
69
+ mapOf(
70
+ "enabled" to simulcastConfig.enabled,
71
+ "activeEncodings" to simulcastConfig.activeEncodings.map { it.rid }
72
+ )
73
+ )
74
+
75
+ fun peersUpdate(peersData: List<Map<String, Any?>>) = EmitableEvent(EventName.PeersUpdate, peersData)
76
+
77
+ fun audioDeviceUpdate(
78
+ audioDevices: List<AudioDevice>,
79
+ selectedDevice: AudioDevice?
80
+ ): EmitableEvent {
81
+ val audioDeviceAsMap = { audioDevice: AudioDevice ->
82
+ mapOf(
83
+ "name" to audioDevice.name,
84
+ "type" to AudioDeviceKind.fromAudioDevice(audioDevice)?.typeName
85
+ )
86
+ }
87
+
88
+ return EmitableEvent(
89
+ EventName.AudioDeviceUpdate,
90
+ mapOf(
91
+ "selectedDevice" to (
92
+ if (selectedDevice != null) {
93
+ audioDeviceAsMap(
94
+ selectedDevice
95
+ )
96
+ } else {
97
+ null
98
+ }
99
+ ),
100
+ "availableDevices" to
101
+ audioDevices.map { audioDevice ->
102
+ audioDeviceAsMap(
103
+ audioDevice
104
+ )
105
+ }
106
+ )
107
+ )
108
+ }
109
+
110
+ val allEvents: Array<String>
111
+ get() = EventName.entries.map { it.name }.toTypedArray()
27
112
  }
28
113
  }
@@ -7,6 +7,8 @@ import android.media.projection.MediaProjectionManager
7
7
  import androidx.appcompat.app.AppCompatActivity
8
8
  import com.fishjamcloud.client.FishjamClient
9
9
  import com.fishjamcloud.client.FishjamClientListener
10
+ import com.fishjamcloud.client.media.CaptureDevice
11
+ import com.fishjamcloud.client.media.CaptureDeviceChangedListener
10
12
  import com.fishjamcloud.client.media.LocalAudioTrack
11
13
  import com.fishjamcloud.client.media.LocalScreenShareTrack
12
14
  import com.fishjamcloud.client.media.LocalVideoTrack
@@ -27,6 +29,7 @@ import com.twilio.audioswitch.AudioDevice
27
29
  import expo.modules.kotlin.AppContext
28
30
  import expo.modules.kotlin.Promise
29
31
  import expo.modules.kotlin.exception.CodedException
32
+ import io.fishjam.reactnative.extensions.toLocalCamera
30
33
  import io.fishjam.reactnative.foregroundService.ForegroundServiceManager
31
34
  import io.fishjam.reactnative.managers.LocalCameraTracksChangedListenersManager
32
35
  import io.fishjam.reactnative.managers.LocalTracksSwitchListenersManager
@@ -40,7 +43,8 @@ import org.webrtc.Logging
40
43
 
41
44
  class RNFishjamClient(
42
45
  val sendEvent: (name: String, data: Map<String, Any?>) -> Unit
43
- ) : FishjamClientListener {
46
+ ) : FishjamClientListener,
47
+ CaptureDeviceChangedListener {
44
48
  private val SCREENSHARE_REQUEST = 1
45
49
 
46
50
  var isMicrophoneOn = false
@@ -71,11 +75,12 @@ class RNFishjamClient(
71
75
  var peerStatus = PeerStatus.idle
72
76
  private set(value) {
73
77
  field = value
74
- val event = EmitableEvents.PeerStatusChanged
75
- emitEvent(event, mapOf(event.name to value))
78
+ emitEvent(EmitableEvent.peerStatusChanged(value))
76
79
  }
77
80
 
78
- private var foregroundServiceManager: ForegroundServiceManager? = null
81
+ private val foregroundServiceManager by lazy {
82
+ ForegroundServiceManager(appContext ?: throw CodedException("appContext not found"))
83
+ }
79
84
 
80
85
  companion object {
81
86
  val trackUpdateListenersManager = TracksUpdateListenersManager()
@@ -254,7 +259,7 @@ class RNFishjamClient(
254
259
  com.fishjamcloud.client.ConnectConfig(
255
260
  url,
256
261
  peerToken,
257
- localUserMetadata,
262
+ peerMetadata,
258
263
  ReconnectConfig(
259
264
  config.reconnectConfig.maxAttempts,
260
265
  config.reconnectConfig.initialDelayMs,
@@ -278,20 +283,22 @@ class RNFishjamClient(
278
283
  }
279
284
  }
280
285
 
281
- suspend fun startCamera(config: CameraConfig) {
286
+ suspend fun startCamera(config: CameraConfig): Boolean {
282
287
  if (isCameraInitialized) {
283
- emitWarning("Camera already started. You may only call startCamera once before leaveRoom is called.")
284
- return
288
+ emitEvent(EmitableEvent.warning("Camera already started. You may only call startCamera once before leaveRoom is called."))
289
+ return true
285
290
  }
286
291
  if (!PermissionUtils.requestCameraPermission(appContext)) {
287
- emitWarning("Camera permission not granted.")
288
- return
292
+ emitEvent(EmitableEvent.warning("Camera permission not granted."))
293
+ return false
289
294
  }
290
295
 
291
296
  val cameraTrack = createCameraTrack(config)
297
+ cameraTrack.captureDeviceChangedListener = this
292
298
  setCameraTrackState(cameraTrack, config.cameraEnabled)
293
299
  emitEndpoints()
294
300
  isCameraInitialized = true
301
+ return true
295
302
  }
296
303
 
297
304
  private suspend fun createCameraTrack(config: CameraConfig): LocalVideoTrack {
@@ -310,8 +317,7 @@ class RNFishjamClient(
310
317
  ) {
311
318
  cameraTrack.setEnabled(isEnabled)
312
319
  isCameraOn = isEnabled
313
- val event = EmitableEvents.IsCameraOn
314
- emitEvent(event, mapOf(event.name to isEnabled))
320
+ emitEvent(EmitableEvent.currentCameraChanged(cameraTrack.getCaptureDevice()?.toLocalCamera(), isEnabled))
315
321
  localCameraTracksChangedListenersManager.notifyListeners()
316
322
  }
317
323
 
@@ -337,7 +343,7 @@ class RNFishjamClient(
337
343
 
338
344
  private suspend fun startMicrophone() {
339
345
  if (!PermissionUtils.requestMicrophonePermission(appContext)) {
340
- emitWarning("Microphone permission not granted.")
346
+ emitEvent(EmitableEvent.warning("Microphone permission not granted."))
341
347
  return
342
348
  }
343
349
 
@@ -352,8 +358,7 @@ class RNFishjamClient(
352
358
  ) {
353
359
  microphoneTrack.setEnabled(isEnabled)
354
360
  isMicrophoneOn = isEnabled
355
- val event = EmitableEvents.IsMicrophoneOn
356
- emitEvent(event, mapOf(event.name to isEnabled))
361
+ emitEvent(EmitableEvent.isMicrophoneOn(isEnabled))
357
362
  }
358
363
 
359
364
  suspend fun toggleMicrophone(): Boolean {
@@ -362,6 +367,15 @@ class RNFishjamClient(
362
367
  } else {
363
368
  getLocalAudioTrack()?.let { setMicrophoneTrackState(it, !isMicrophoneOn) }
364
369
  }
370
+
371
+ updateLocalAudioTrackMetadata(
372
+ mapOf(
373
+ "active" to isMicrophoneOn,
374
+ "paused" to !isMicrophoneOn, // TODO: FCE-711,
375
+ "type" to "microphone"
376
+ )
377
+ )
378
+
365
379
  return isMicrophoneOn
366
380
  }
367
381
 
@@ -380,22 +394,13 @@ class RNFishjamClient(
380
394
  }
381
395
  }
382
396
 
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)
397
+ suspend fun startForegroundService(config: ForegroundServiceConfig) {
398
+ foregroundServiceManager.updateServiceWithConfig(config)
399
+ foregroundServiceManager.start()
395
400
  }
396
401
 
397
402
  fun stopForegroundService() {
398
- foregroundServiceManager?.stopForegroundService()
403
+ foregroundServiceManager.stop()
399
404
  }
400
405
 
401
406
  suspend fun toggleScreenShare(screenShareOptions: ScreenShareOptions) {
@@ -408,6 +413,7 @@ class RNFishjamClient(
408
413
  screenShareOptions.maxBandwidthMap,
409
414
  screenShareOptions.maxBandwidthInt
410
415
  )
416
+
411
417
  if (!isScreenShareOn) {
412
418
  ensureConnected()
413
419
  startScreenShare()
@@ -421,7 +427,6 @@ class RNFishjamClient(
421
427
  mapOf(
422
428
  "id" to endpoint.id,
423
429
  "isLocal" to (endpoint.id == fishjamClient.getLocalEndpoint().id),
424
- "type" to endpoint.type,
425
430
  "metadata" to endpoint.metadata,
426
431
  "tracks" to
427
432
  endpoint.tracks.values.mapNotNull { track ->
@@ -475,19 +480,12 @@ class RNFishjamClient(
475
480
  fun getCaptureDevices(): List<Map<String, Any>> {
476
481
  val devices = LocalVideoTrack.getCaptureDevices(appContext?.reactContext!!)
477
482
  return devices.map { device ->
478
- mapOf<String, Any>(
479
- "id" to device.deviceName,
480
- "name" to device.deviceName,
481
- "facingDirection" to
482
- when (true) {
483
- device.isFrontFacing -> "front"
484
- device.isBackFacing -> "back"
485
- else -> "unspecified"
486
- }
487
- )
483
+ device.toLocalCamera()
488
484
  }
489
485
  }
490
486
 
487
+ fun getCurrentCaptureDevice(): Map<String, Any>? = getLocalVideoTrack()?.getCaptureDevice()?.toLocalCamera()
488
+
491
489
  fun updatePeerMetadata(metadata: Metadata) {
492
490
  ensureConnected()
493
491
  fishjamClient.updatePeerMetadata(metadata)
@@ -508,7 +506,7 @@ class RNFishjamClient(
508
506
  }
509
507
  }
510
508
 
511
- fun updateLocalAudioTrackMetadata(metadata: Metadata) {
509
+ private fun updateLocalAudioTrackMetadata(metadata: Metadata) {
512
510
  ensureAudioTrack()
513
511
  getLocalAudioTrack()?.let {
514
512
  updateTrackMetadata(it.id(), metadata)
@@ -606,13 +604,15 @@ class RNFishjamClient(
606
604
  fishjamClient.setTargetTrackEncoding(trackId, encoding.toTrackEncoding())
607
605
  }
608
606
 
609
- fun toggleVideoTrackEncoding(encoding: String): Map<String, Any> {
607
+ fun toggleVideoTrackEncoding(encoding: String): Map<String, Any?> {
610
608
  ensureVideoTrack()
611
609
  val trackId = getLocalVideoTrack()?.id() ?: return emptyMap()
612
610
  videoSimulcastConfig = toggleTrackEncoding(encoding, trackId, videoSimulcastConfig)
613
- val eventName = EmitableEvents.SimulcastConfigUpdate
614
- emitEvent(eventName, getSimulcastConfigAsRNMap(videoSimulcastConfig))
615
- return getSimulcastConfigAsRNMap(videoSimulcastConfig)
611
+
612
+ val event = EmitableEvent.simulcastConfigUpdate(videoSimulcastConfig)
613
+ emitEvent(event)
614
+
615
+ return event.data
616
616
  }
617
617
 
618
618
  fun setVideoTrackEncodingBandwidth(
@@ -712,6 +712,10 @@ class RNFishjamClient(
712
712
  if (mediaProjectionIntent == null) {
713
713
  throw MissingScreenSharePermission()
714
714
  }
715
+
716
+ foregroundServiceManager.updateService { screenSharingEnabled = true }
717
+ foregroundServiceManager.start()
718
+
715
719
  fishjamClient.createScreenShareTrack(
716
720
  mediaProjectionIntent!!,
717
721
  videoParameters,
@@ -743,13 +747,14 @@ class RNFishjamClient(
743
747
 
744
748
  private fun setScreenShareTrackState(isEnabled: Boolean) {
745
749
  isScreenShareOn = isEnabled
746
- val event = EmitableEvents.IsScreenShareOn
747
- emitEvent(event, mapOf(event.name to isEnabled))
750
+ emitEvent(EmitableEvent.isScreenShareOn(isEnabled))
748
751
  }
749
752
 
750
753
  private fun stopScreenShare() {
751
754
  ensureScreenShareTrack()
752
755
  coroutineScope.launch {
756
+ foregroundServiceManager.updateService { screenSharingEnabled = false }
757
+ foregroundServiceManager.start()
753
758
  val screenShareTrack =
754
759
  fishjamClient.getLocalEndpoint().tracks.values.first { track ->
755
760
  track is LocalScreenShareTrack
@@ -762,58 +767,21 @@ class RNFishjamClient(
762
767
  }
763
768
  }
764
769
 
765
- private fun emitEvent(
766
- event: EmitableEvents,
767
- data: Map<String, Any?> = mapOf()
768
- ) {
770
+ private fun emitEvent(event: EmitableEvent) {
769
771
  CoroutineScope(Dispatchers.Main).launch {
770
- sendEvent(event.name, data)
772
+ sendEvent(event.name, event.data)
771
773
  }
772
774
  }
773
775
 
774
- fun emitWarning(warning: String) {
775
- emitEvent(EmitableEvents.Warning, mapOf("message" to warning))
776
- }
777
-
778
776
  private fun emitEndpoints() {
779
- val event = EmitableEvents.PeersUpdate
780
- emitEvent(event, mapOf(event.name to getPeers()))
777
+ emitEvent(EmitableEvent.peersUpdate(getPeers()))
781
778
  }
782
779
 
783
- private fun audioDeviceAsRNMap(audioDevice: AudioDevice): Map<String, String?> =
784
- mapOf(
785
- "name" to audioDevice.name,
786
- "type" to AudioDeviceKind.fromAudioDevice(audioDevice)?.typeName
787
- )
788
-
789
780
  private fun emitAudioDeviceEvent(
790
781
  audioDevices: List<AudioDevice>,
791
782
  selectedDevice: AudioDevice?
792
783
  ) {
793
- val event = EmitableEvents.AudioDeviceUpdate
794
- emitEvent(
795
- event,
796
- mapOf(
797
- event.name to
798
- mapOf(
799
- "selectedDevice" to (
800
- if (selectedDevice != null) {
801
- audioDeviceAsRNMap(
802
- selectedDevice
803
- )
804
- } else {
805
- null
806
- }
807
- ),
808
- "availableDevices" to
809
- audioDevices.map { audioDevice ->
810
- audioDeviceAsRNMap(
811
- audioDevice
812
- )
813
- }
814
- )
815
- )
816
- )
784
+ emitEvent(EmitableEvent.audioDeviceUpdate(audioDevices, selectedDevice))
817
785
  }
818
786
 
819
787
  private fun getSimulcastConfigAsRNMap(simulcastConfig: SimulcastConfig): Map<String, Any> =
@@ -885,8 +853,7 @@ class RNFishjamClient(
885
853
  override fun onPeerUpdated(peer: Peer) {}
886
854
 
887
855
  override fun onBandwidthEstimationChanged(estimation: Long) {
888
- val event = EmitableEvents.BandwidthEstimation
889
- emitEvent(event, mapOf(event.name to estimation.toFloat()))
856
+ emitEvent(EmitableEvent.bandwidthEstimation(estimation))
890
857
  }
891
858
 
892
859
  override fun onDisconnected() {
@@ -912,14 +879,18 @@ class RNFishjamClient(
912
879
  }
913
880
 
914
881
  override fun onReconnected() {
915
- emitEvent(EmitableEvents.Reconnected)
882
+ emitEvent(EmitableEvent.reconnected)
916
883
  }
917
884
 
918
885
  override fun onReconnectionStarted() {
919
- emitEvent(EmitableEvents.ReconnectionStarted)
886
+ emitEvent(EmitableEvent.reconnectionStarted)
920
887
  }
921
888
 
922
889
  override fun onReconnectionRetriesLimitReached() {
923
- emitEvent(EmitableEvents.ReconnectionRetriesLimitReached)
890
+ emitEvent(EmitableEvent.reconnectionRetriesLimitReached)
891
+ }
892
+
893
+ override fun onCaptureDeviceChanged(captureDevice: CaptureDevice?) {
894
+ emitEvent(EmitableEvent.currentCameraChanged(captureDevice?.toLocalCamera(), isCameraOn))
924
895
  }
925
896
  }
@@ -81,7 +81,7 @@ class ConnectConfig : Record {
81
81
  val reconnectConfig: ReconnectConfig = ReconnectConfig()
82
82
  }
83
83
 
84
- class ForegroundServiceNotificationConfig : Record {
84
+ class ForegroundServiceConfig : Record {
85
85
  @Field
86
86
  val channelId: String? = null
87
87
 
@@ -93,17 +93,12 @@ class ForegroundServiceNotificationConfig : Record {
93
93
 
94
94
  @Field
95
95
  val notificationTitle: String? = null
96
- }
97
-
98
- class ForegroundServicePermissionsConfig : Record {
99
- @Field
100
- val enableCamera: Boolean = false
101
96
 
102
97
  @Field
103
- val enableMicrophone: Boolean = false
98
+ val enableCamera: Boolean? = null
104
99
 
105
100
  @Field
106
- val enableScreenSharing: Boolean = false
101
+ val enableMicrophone: Boolean? = null
107
102
  }
108
103
 
109
104
  class RNFishjamClientModule : Module() {
@@ -118,7 +113,7 @@ class RNFishjamClientModule : Module() {
118
113
 
119
114
  Name("RNFishjamClient")
120
115
 
121
- Events(EmitableEvents.allEvents)
116
+ Events(EmitableEvent.allEvents)
122
117
 
123
118
  OnCreate {
124
119
  rnFishjamClient.onModuleCreate(appContext)
@@ -148,6 +143,10 @@ class RNFishjamClientModule : Module() {
148
143
  return@Property rnFishjamClient.getCaptureDevices()
149
144
  }
150
145
 
146
+ Property("currentCamera") {
147
+ return@Property rnFishjamClient.getCurrentCaptureDevice()
148
+ }
149
+
151
150
  Property("isScreenShareOn") {
152
151
  return@Property rnFishjamClient.isScreenShareOn
153
152
  }
@@ -171,8 +170,8 @@ class RNFishjamClientModule : Module() {
171
170
  }
172
171
 
173
172
  AsyncFunction("startCamera") Coroutine { config: CameraConfig ->
174
- withContext(Dispatchers.Main) {
175
- rnFishjamClient.startCamera(config)
173
+ return@Coroutine withContext(Dispatchers.Main) {
174
+ return@withContext rnFishjamClient.startCamera(config)
176
175
  }
177
176
  }
178
177
 
@@ -234,12 +233,6 @@ class RNFishjamClientModule : Module() {
234
233
  }
235
234
  }
236
235
 
237
- AsyncFunction("updateAudioTrackMetadata") Coroutine { metadata: Map<String, Any> ->
238
- withContext(Dispatchers.Main) {
239
- rnFishjamClient.updateLocalAudioTrackMetadata(metadata)
240
- }
241
- }
242
-
243
236
  AsyncFunction("updateScreenShareTrackMetadata") Coroutine { metadata: Map<String, Any> ->
244
237
  withContext(Dispatchers.Main) {
245
238
  rnFishjamClient.updateLocalScreenShareTrackMetadata(metadata)
@@ -301,18 +294,14 @@ class RNFishjamClientModule : Module() {
301
294
  }
302
295
 
303
296
  AsyncFunction("changeWebRTCLoggingSeverity") Coroutine { severity: String ->
304
- CoroutineScope(Dispatchers.Main).launch {
297
+ withContext(Dispatchers.Main) {
305
298
  rnFishjamClient.changeWebRTCLoggingSeverity(severity)
306
299
  }
307
300
  }
308
301
 
309
302
  AsyncFunction("getStatistics") { rnFishjamClient.getStatistics() }
310
303
 
311
- Function("configureForegroundService") { config: ForegroundServiceNotificationConfig ->
312
- return@Function rnFishjamClient.configureForegroundService(config)
313
- }
314
-
315
- AsyncFunction("startForegroundService") Coroutine { config: ForegroundServicePermissionsConfig ->
304
+ AsyncFunction("startForegroundService") Coroutine { config: ForegroundServiceConfig ->
316
305
  rnFishjamClient.startForegroundService(config)
317
306
  }
318
307
 
@@ -15,13 +15,17 @@ class VideoRendererViewModule : Module() {
15
15
  RNFishjamClient.localTracksSwitchListenerManager.remove(view)
16
16
  }
17
17
 
18
- Prop("trackId") { view: VideoRendererView, trackId: String ->
18
+ Prop("trackId") { view, trackId: String ->
19
19
  view.init(trackId)
20
20
  }
21
21
 
22
- Prop("videoLayout") { view: VideoRendererView, videoLayout: String ->
22
+ Prop("videoLayout") { view, videoLayout: String ->
23
23
  view.setVideoLayout(videoLayout)
24
24
  }
25
+
26
+ Prop("skipRenderOutsideVisibleArea") { view, skipRenderOutsideVisibleArea: Boolean ->
27
+ view.checkVisibilityDelayMillis = if (skipRenderOutsideVisibleArea) 1000 else null
28
+ }
25
29
  }
26
30
  }
27
31
  }
@@ -2,16 +2,18 @@ package io.fishjam.reactnative
2
2
 
3
3
  import android.animation.ValueAnimator
4
4
  import android.content.Context
5
+ import android.content.res.Resources
5
6
  import android.graphics.Color
7
+ import android.graphics.Rect
6
8
  import android.graphics.drawable.ColorDrawable
9
+ import android.os.Handler
10
+ import android.os.Looper
7
11
  import android.view.animation.LinearInterpolator
8
12
  import com.fishjamcloud.client.media.LocalVideoTrack
9
13
  import com.fishjamcloud.client.media.VideoTrack
10
14
  import expo.modules.kotlin.AppContext
11
15
  import expo.modules.kotlin.views.ExpoView
12
16
  import io.fishjam.reactnative.managers.LocalTrackSwitchListener
13
- import kotlinx.coroutines.CoroutineScope
14
- import kotlinx.coroutines.Dispatchers
15
17
  import kotlinx.coroutines.delay
16
18
  import org.webrtc.RendererCommon
17
19
 
@@ -35,7 +37,41 @@ abstract class VideoView(
35
37
  }
36
38
  }
37
39
 
38
- val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
40
+ private val checkVisibilityHandler: Handler = Handler(Looper.getMainLooper())
41
+
42
+ // If set to `null` the view will always be rendered
43
+ var checkVisibilityDelayMillis: Long? = null
44
+ set(value) {
45
+ field = value
46
+ if (value == null) {
47
+ getVideoTrack()?.shouldReceive(true)
48
+ } else {
49
+ checkVisibilityHandler.post(checkVisibility)
50
+ }
51
+ }
52
+
53
+ private val checkVisibility =
54
+ object : Runnable {
55
+ override fun run() {
56
+ checkVisibilityDelayMillis?.let {
57
+ getVideoTrack()?.shouldReceive(isVisibleOnScreen)
58
+ checkVisibilityHandler.postDelayed(this, it)
59
+ }
60
+ }
61
+ }
62
+
63
+ private val isVisibleOnScreen: Boolean
64
+ get() {
65
+ if (!isShown) {
66
+ return false
67
+ }
68
+
69
+ val globalVisibleRect = Rect()
70
+ getGlobalVisibleRect(globalVisibleRect)
71
+ val displayMetrics = Resources.getSystem().displayMetrics
72
+ val screen = Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)
73
+ return screen.intersect(globalVisibleRect)
74
+ }
39
75
 
40
76
  init {
41
77
  RNFishjamClient.localTracksSwitchListenerManager.add(this)
@@ -53,6 +89,7 @@ abstract class VideoView(
53
89
  }
54
90
 
55
91
  open fun dispose() {
92
+ checkVisibilityHandler.removeCallbacksAndMessages(null)
56
93
  videoView.release()
57
94
  }
58
95
 
@@ -74,5 +111,6 @@ abstract class VideoView(
74
111
 
75
112
  fun setupTrack() {
76
113
  videoView.setMirror((getVideoTrack() as? LocalVideoTrack)?.isFrontCamera() ?: false)
114
+ checkVisibilityHandler.post(checkVisibility)
77
115
  }
78
116
  }
@@ -0,0 +1,17 @@
1
+ package io.fishjam.reactnative.extensions
2
+
3
+ import com.fishjamcloud.client.media.CaptureDevice
4
+
5
+ typealias LocalCamera = Map<String, Any>
6
+
7
+ fun CaptureDevice.toLocalCamera(): LocalCamera =
8
+ mapOf<String, Any>(
9
+ "id" to deviceName,
10
+ "name" to deviceName,
11
+ "facingDirection" to
12
+ when (true) {
13
+ isFrontFacing -> "front"
14
+ isBackFacing -> "back"
15
+ else -> "unspecified"
16
+ }
17
+ )