@fishjam-cloud/react-native-client 0.4.0 → 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 (90) 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/Utils/PermissionUtils.swift +16 -0
  84. package/ios/VideoPreviewView.swift +13 -13
  85. package/ios/VideoRendererView.swift +28 -25
  86. package/ios/VideoRendererViewModule.swift +8 -4
  87. package/ios/extensions/AVCaptureDevice.swift +20 -0
  88. package/ios/managers/LocalCameraTracksChangedListenersManager.swift +23 -0
  89. package/ios/managers/TracksUpdateListenersManager.swift +21 -0
  90. package/package.json +9 -11
@@ -26,22 +26,23 @@ class RNFishjamClient: FishjamClientListener {
26
26
 
27
27
  var cameraId: String? = nil
28
28
 
29
- var audioSessionMode: AVAudioSession.Mode = AVAudioSession.Mode.videoChat
29
+ var audioSessionMode: AVAudioSession.Mode = .videoChat
30
30
  var errorMessage: String?
31
31
 
32
+ var currentCamera: LocalCamera? { getLocalCameraTrack()?.currentCaptureDevice?.toLocalCamera() }
33
+
32
34
  private(set) var peerStatus: PeerStatus = .idle {
33
35
  didSet {
34
- let event = EmitableEvents.PeerStatusChanged
35
- emit(event: event, data: [event.name: peerStatus.rawValue])
36
+ emit(event: .peerStatusChanged(peerStatus: peerStatus))
36
37
  }
37
38
  }
38
39
 
39
- let sendEvent: (_ eventName: String, _ data: [String: Any]) -> Void
40
+ let sendEvent: (_ eventName: String, _ data: [String: Any?]) -> Void
40
41
 
41
42
  static var tracksUpdateListenersManager = TracksUpdateListenersManager()
42
43
  static var localCameraTracksChangedListenersManager = LocalCameraTracksChangedListenersManager()
43
44
 
44
- init(sendEvent: @escaping (_ eventName: String, _ data: [String: Any]) -> Void) {
45
+ init(sendEvent: @escaping (_ eventName: String, _ data: [String: Any?]) -> Void) {
45
46
  self.sendEvent = sendEvent
46
47
  NotificationCenter.default.addObserver(
47
48
  self,
@@ -214,7 +215,7 @@ class RNFishjamClient: FishjamClientListener {
214
215
 
215
216
  RNFishjamClient.fishjamClient?.connect(
216
217
  config: FishjamCloudClient.ConnectConfig(
217
- websocketUrl: url, token: peerToken, peerMetadata: localUserMetadata,
218
+ websocketUrl: url, token: peerToken, peerMetadata: peerMetadata.toMetadata(),
218
219
  reconnectConfig: reconnectConfig
219
220
  ))
220
221
 
@@ -239,23 +240,36 @@ class RNFishjamClient: FishjamClientListener {
239
240
  }
240
241
  }
241
242
 
242
- func startCamera(config: CameraConfig) async throws {
243
- try ensureCreated()
243
+ func startCamera(config: CameraConfig) async throws -> Bool {
244
+ #if targetEnvironment(simulator)
245
+ emit(
246
+ event: .warning(
247
+ message: "Camera is not supported on simulator."))
248
+ return false
249
+ #else
250
+ try ensureCreated()
244
251
 
245
- guard !isCameraInitialized else {
246
- emit(warning: "Camera already started. You may only call startCamera once before leaveRoom is called.")
247
- return
248
- }
252
+ guard !isCameraInitialized else {
253
+ emit(
254
+ event: .warning(
255
+ message:
256
+ "Camera already started. You may only call startCamera once before leaveRoom is called."))
249
257
 
250
- guard await PermissionUtils.requestCameraPermission() else {
251
- emit(warning: "Camera permission not granted.")
252
- return
253
- }
258
+ return true
259
+ }
254
260
 
255
- let cameraTrack = try createCameraTrack(config: config)
256
- setCameraTrackState(cameraTrack, enabled: config.cameraEnabled)
257
- emitEndpoints()
258
- isCameraInitialized = true
261
+ guard await PermissionUtils.requestCameraPermission() else {
262
+ emit(event: .warning(message: "Camera permission not granted."))
263
+ return false
264
+ }
265
+
266
+ let cameraTrack = try createCameraTrack(config: config)
267
+ cameraTrack.captureDeviceChangedListener = self
268
+ setCameraTrackState(cameraTrack, enabled: config.cameraEnabled)
269
+ emitEndpoints()
270
+ isCameraInitialized = true
271
+ return true
272
+ #endif
259
273
  }
260
274
 
261
275
  private func createCameraTrack(config: CameraConfig) throws -> LocalCameraTrack {
@@ -272,9 +286,9 @@ class RNFishjamClient: FishjamClientListener {
272
286
  private func setCameraTrackState(_ cameraTrack: LocalCameraTrack, enabled: Bool) {
273
287
  cameraTrack.enabled = enabled
274
288
  isCameraOn = enabled
275
- let event = EmitableEvents.IsCameraOn
276
- let isCameraEnabledMap = [event.name: enabled]
277
- emit(event: event, data: isCameraEnabledMap)
289
+ emit(
290
+ event: .currentCameraChanged(
291
+ localCamera: cameraTrack.currentCaptureDevice?.toLocalCamera(), isCameraOn: enabled))
278
292
  RNFishjamClient.localCameraTracksChangedListenersManager.notifyListeners()
279
293
  }
280
294
 
@@ -301,12 +315,19 @@ class RNFishjamClient: FishjamClientListener {
301
315
  } else {
302
316
  try await startMicrophone()
303
317
  }
318
+
319
+ try updateLocalAudioTrackMetadata(metadata: [
320
+ "active": isMicrophoneOn,
321
+ "paused": !isMicrophoneOn, //TODO: FCE-711
322
+ "type": "microphone",
323
+ ])
324
+
304
325
  return isMicrophoneOn
305
326
  }
306
327
 
307
328
  func startMicrophone() async throws {
308
329
  guard await PermissionUtils.requestMicrophonePermission() else {
309
- emit(warning: "Microphone permission not granted.")
330
+ emit(event: .warning(message: "Microphone permission not granted."))
310
331
  return
311
332
  }
312
333
  let microphoneTrack = RNFishjamClient.fishjamClient!.createAudioTrack(metadata: Metadata())
@@ -318,9 +339,7 @@ class RNFishjamClient: FishjamClientListener {
318
339
  private func setMicrophoneTrackState(_ microphoneTrack: LocalAudioTrack, enabled: Bool) {
319
340
  microphoneTrack.enabled = enabled
320
341
  isMicrophoneOn = enabled
321
- let event = EmitableEvents.IsMicrophoneOn
322
- let isMicrophoneOnMap = [event.name: enabled]
323
- emit(event: event, data: isMicrophoneOnMap)
342
+ emit(event: .isMicrophoneOn(enabled: enabled))
324
343
  }
325
344
 
326
345
  func setAudioSessionMode() {
@@ -345,7 +364,7 @@ class RNFishjamClient: FishjamClientListener {
345
364
  try ensureCreated()
346
365
  try ensureConnected()
347
366
  guard isAppScreenShareOn == false else {
348
- emit(warning: "Screensharing screen not available during screensharing app.")
367
+ emit(event: .warning(message: "Screensharing screen not available during screensharing app."))
349
368
  return
350
369
  }
351
370
  guard let screenShareExtensionBundleId = Bundle.main.infoDictionary?["ScreenShareExtensionBundleId"] as? String
@@ -383,7 +402,7 @@ class RNFishjamClient: FishjamClientListener {
383
402
  metadata: screenShareMetadata,
384
403
  canStart: {
385
404
  if self.isAppScreenShareOn {
386
- self.emit(warning: "Screensharing screen not available during screensharing app.")
405
+ self.emit(event: .warning(message: "Screensharing screen not available during screensharing app."))
387
406
  }
388
407
  return !self.isAppScreenShareOn
389
408
  },
@@ -418,9 +437,7 @@ class RNFishjamClient: FishjamClientListener {
418
437
 
419
438
  private func setScreenShareTrackState(enabled: Bool) throws {
420
439
  isScreenShareOn = enabled
421
- let event = EmitableEvents.IsScreenShareOn
422
- let isScreenShareEnabled = [event.name: enabled]
423
- emit(event: event, data: isScreenShareEnabled)
440
+ emit(event: .isScreenShareOn(enabled: enabled))
424
441
  }
425
442
 
426
443
  func startScreenAppShare(screenShareOptions: ScreenShareOptions) throws {
@@ -449,7 +466,7 @@ class RNFishjamClient: FishjamClientListener {
449
466
 
450
467
  func toggleAppScreenShare(screenShareOptions: ScreenShareOptions) throws {
451
468
  guard isScreenShareOn == false else {
452
- emit(warning: "App screensharing not available during screensharing.")
469
+ emit(event: .warning(message: "App screensharing not available during screensharing."))
453
470
  return
454
471
  }
455
472
  if getLocalScreenAppTrack() != nil {
@@ -462,9 +479,7 @@ class RNFishjamClient: FishjamClientListener {
462
479
  private func setScreenAppTrackState(_ track: LocalAppScreenShareTrack, enabled: Bool) {
463
480
  track.enabled = enabled
464
481
  isAppScreenShareOn = enabled
465
- let event = EmitableEvents.IsAppScreenShareOn
466
- let eventMap = [event.name: enabled]
467
- emit(event: event, data: eventMap)
482
+ emit(event: .isAppScreenShareOn(enabled: enabled))
468
483
  }
469
484
 
470
485
  //returns local endpoint and remote endpoints
@@ -480,7 +495,6 @@ class RNFishjamClient: FishjamClientListener {
480
495
  [
481
496
  "id": endpoint.id,
482
497
  "isLocal": endpoint.id == RNFishjamClient.fishjamClient!.getLocalEndpoint().id,
483
- "type": endpoint.type,
484
498
  "metadata": endpoint.metadata.toDict(),
485
499
  "tracks": endpoint.tracks.values.compactMap { track -> [String: Any?]? in
486
500
  switch track {
@@ -539,19 +553,7 @@ class RNFishjamClient: FishjamClientListener {
539
553
 
540
554
  func getCaptureDevices() -> [[String: Any]] {
541
555
  let devices = LocalCameraTrack.getCaptureDevices()
542
- return devices.map { device -> [String: Any] in
543
- let facingDirection =
544
- switch device.position {
545
- case .front: "front"
546
- case .back: "back"
547
- default: "unspecified"
548
- }
549
- return [
550
- "id": device.uniqueID,
551
- "name": device.localizedName,
552
- "facingDirection": facingDirection,
553
- ]
554
- }
556
+ return devices.map { $0.toLocalCamera() }
555
557
  }
556
558
 
557
559
  func updatePeerMetadata(metadata: [String: Any]) throws {
@@ -571,7 +573,7 @@ class RNFishjamClient: FishjamClientListener {
571
573
  }
572
574
  }
573
575
 
574
- func updateLocalAudioTrackMetadata(metadata: [String: Any]) throws {
576
+ private func updateLocalAudioTrackMetadata(metadata: [String: Any]) throws {
575
577
  try ensureAudioTrack()
576
578
  if let track = getLocalAudioTrack() {
577
579
  updateTrackMetadata(trackId: track.id, metadata: metadata)
@@ -604,13 +606,14 @@ class RNFishjamClient: FishjamClientListener {
604
606
  return SimulcastConfig(enabled: true, activeEncodings: updatedEncodings)
605
607
  }
606
608
 
607
- func toggleScreenShareTrackEncoding(encoding: String) throws -> [String: Any] {
609
+ func toggleScreenShareTrackEncoding(encoding: String) throws -> [String: Any?] {
608
610
  try ensureScreenBroadcastTrack()
609
611
  if let track = getLocalScreenBroadcastTrack() {
610
612
  screenShareSimulcastConfig = try toggleTrackEncoding(
611
613
  encoding: encoding, trackId: track.id, simulcastConfig: screenShareSimulcastConfig)
612
614
  }
613
- return getSimulcastConfigAsRNMap(screenShareSimulcastConfig)
615
+
616
+ return EmitableEvent.simulcastConfigUpdate(simulcastConfig: screenShareSimulcastConfig).data
614
617
  }
615
618
 
616
619
  func setScreenShareTrackBandwidth(bandwidth: Int) throws {
@@ -634,7 +637,7 @@ class RNFishjamClient: FishjamClientListener {
634
637
  RNFishjamClient.fishjamClient?.setTargetTrackEncoding(trackId: trackId, encoding: trackEncoding)
635
638
  }
636
639
 
637
- func toggleVideoTrackEncoding(encoding: String) throws -> [String: Any] {
640
+ func toggleVideoTrackEncoding(encoding: String) throws -> [String: Any?] {
638
641
  try ensureCameraTrack()
639
642
  try ensureConnected()
640
643
 
@@ -642,11 +645,10 @@ class RNFishjamClient: FishjamClientListener {
642
645
  videoSimulcastConfig = try toggleTrackEncoding(
643
646
  encoding: encoding, trackId: track.id, simulcastConfig: videoSimulcastConfig)
644
647
 
645
- let event = EmitableEvents.SimulcastConfigUpdate
646
- let simulcastConfigAsRNMap = getSimulcastConfigAsRNMap(videoSimulcastConfig)
647
- emit(event: event, data: simulcastConfigAsRNMap)
648
+ let event = EmitableEvent.simulcastConfigUpdate(simulcastConfig: videoSimulcastConfig)
649
+ emit(event: event)
648
650
 
649
- return simulcastConfigAsRNMap
651
+ return event.data
650
652
  }
651
653
 
652
654
  func setVideoTrackEncodingBandwidth(encoding: String, bandwidth: Int) throws {
@@ -759,20 +761,14 @@ class RNFishjamClient: FishjamClientListener {
759
761
  )
760
762
  }
761
763
 
762
- func emit(event: EmitableEvents, data: [String: Any] = [:]) {
764
+ func emit(event: EmitableEvent) {
763
765
  DispatchQueue.main.async { [weak self] in
764
- self?.sendEvent(event.name, data)
766
+ self?.sendEvent(event.event.name, event.data)
765
767
  }
766
768
  }
767
769
 
768
- func emit(warning: String) {
769
- emit(event: .Warning, data: ["message": warning])
770
- }
771
-
772
770
  func emitEndpoints() {
773
- let event = EmitableEvents.PeersUpdate
774
- let EndpointsUpdateMap = [event.name: (try? getPeers()) ?? []]
775
- emit(event: event, data: EndpointsUpdateMap)
771
+ emit(event: .peersUpdate(peersData: (try? getPeers()) ?? []))
776
772
  }
777
773
 
778
774
  func onJoined(peerID: String, peersInRoom: [String: Endpoint]) {
@@ -856,13 +852,6 @@ class RNFishjamClient: FishjamClientListener {
856
852
  peerStatus = .idle
857
853
  }
858
854
 
859
- func getSimulcastConfigAsRNMap(_ simulcastConfig: SimulcastConfig) -> [String: Any] {
860
- return [
861
- "enabled": simulcastConfig.enabled,
862
- "activeEncodings": simulcastConfig.activeEncodings.map { e in e.description },
863
- ]
864
- }
865
-
866
855
  func selectAudioSessionMode(sessionMode: String) throws {
867
856
  switch sessionMode {
868
857
  case "videoChat":
@@ -895,53 +884,29 @@ class RNFishjamClient: FishjamClientListener {
895
884
  }
896
885
 
897
886
  @objc func onRouteChangeNotification() {
898
- let currentRoute = AVAudioSession.sharedInstance().currentRoute
899
- let output = currentRoute.outputs[0]
900
- let deviceType = output.portType
901
- var deviceTypeString: String = ""
902
-
903
- switch deviceType {
904
- case .bluetoothA2DP, .bluetoothLE, .bluetoothHFP:
905
- deviceTypeString = "bluetooth"
906
- break
907
- case .builtInSpeaker:
908
- deviceTypeString = "speaker"
909
- break
910
- case .builtInReceiver:
911
- deviceTypeString = "earpiece"
912
- break
913
- case .headphones:
914
- deviceTypeString = "headphones"
915
- break
916
- default:
917
- deviceTypeString = deviceType.rawValue
918
- }
919
- let event = EmitableEvents.AudioDeviceUpdate
920
- emit(
921
- event: event,
922
- data: [
923
- event.name: [
924
- "selectedDevice": ["name": output.portName, "type": deviceTypeString],
925
- "availableDevices": [],
926
- ]
927
- ] as [String: [String: Any]])
887
+ emit(event: .audioDeviceUpdate(currentRoute: AVAudioSession.sharedInstance().currentRoute))
928
888
  }
929
889
 
930
890
  func onBandwidthEstimationChanged(estimation: Int) {
931
- let event = EmitableEvents.BandwidthEstimation
932
- emit(event: event, data: [event.name: estimation])
891
+ emit(event: .bandwidthEstimation(estimation: estimation))
933
892
  }
934
893
 
935
894
  func onReconnectionStarted() {
936
- emit(event: .ReconnectionStarted)
895
+ emit(event: .reconnectionStarted)
937
896
  }
938
897
 
939
898
  func onReconnected() {
940
- emit(event: .Reconnected)
899
+ emit(event: .reconnected)
941
900
  }
942
901
 
943
902
  func onReconnectionRetriesLimitReached() {
944
- emit(event: .ReconnectionRetriesLimitReached)
903
+ emit(event: .reconnectionRetriesLimitReached)
945
904
  }
946
905
 
947
906
  }
907
+
908
+ extension RNFishjamClient: CameraCapturerDeviceChangedListener {
909
+ func onCaptureDeviceChanged(_ device: AVCaptureDevice?) {
910
+ emit(event: .currentCameraChanged(localCamera: device?.toLocalCamera(), isCameraOn: isCameraOn))
911
+ }
912
+ }
@@ -75,7 +75,7 @@ public class RNFishjamClientModule: Module {
75
75
  public func definition() -> ModuleDefinition {
76
76
  let rnFishjamClient: RNFishjamClient = {
77
77
  let client = RNFishjamClient {
78
- (eventName: String, data: [String: Any]) in
78
+ (eventName: String, data: [String: Any?]) in
79
79
  self.sendEvent(eventName, data)
80
80
  }
81
81
  client.create()
@@ -84,7 +84,7 @@ public class RNFishjamClientModule: Module {
84
84
 
85
85
  Name("RNFishjamClient")
86
86
 
87
- Events(EmitableEvents.allEvents)
87
+ Events(EmitableEvent.allEvents)
88
88
 
89
89
  Property("peerStatus") {
90
90
  return rnFishjamClient.peerStatus.rawValue
@@ -102,6 +102,10 @@ public class RNFishjamClientModule: Module {
102
102
  return rnFishjamClient.getCaptureDevices()
103
103
  }
104
104
 
105
+ Property("currentCamera") {
106
+ return rnFishjamClient.currentCamera
107
+ }
108
+
105
109
  Property("isScreenShareOn") {
106
110
  return rnFishjamClient.isScreenShareOn
107
111
  }
@@ -164,10 +168,6 @@ public class RNFishjamClientModule: Module {
164
168
  try rnFishjamClient.updateLocalVideoTrackMetadata(metadata: metadata)
165
169
  }
166
170
 
167
- AsyncFunction("updateAudioTrackMetadata") { (metadata: [String: Any]) in
168
- try rnFishjamClient.updateLocalAudioTrackMetadata(metadata: metadata)
169
- }
170
-
171
171
  AsyncFunction("updateScreenShareTrackMetadata") { (metadata: [String: Any]) in
172
172
  try rnFishjamClient.updateLocalScreenShareTrackMetadata(metadata: metadata)
173
173
  }
@@ -0,0 +1,16 @@
1
+ import AVFoundation
2
+
3
+ class PermissionUtils {
4
+ static func requestCameraPermission() async -> Bool { await requestAccessIfNeeded(for: .video) }
5
+ static func requestMicrophonePermission() async -> Bool { await requestAccessIfNeeded(for: .audio) }
6
+
7
+ private static func requestAccessIfNeeded(for mediaType: AVMediaType) async -> Bool {
8
+ let status = AVCaptureDevice.authorizationStatus(for: mediaType)
9
+
10
+ return if status == .notDetermined {
11
+ await AVCaptureDevice.requestAccess(for: mediaType)
12
+ } else {
13
+ status == .authorized
14
+ }
15
+ }
16
+ }
@@ -2,15 +2,14 @@ import ExpoModulesCore
2
2
  import FishjamCloudClient
3
3
 
4
4
  class VideoPreviewView: ExpoView, LocalCameraTrackChangedListener {
5
- var videoView: VideoView? = nil
6
- private var localVideoTrack: LocalCameraTrack? = nil
5
+ var videoView: VideoView!
6
+ private var localVideoTrack: LocalCameraTrack?
7
7
 
8
8
  required init(appContext: AppContext? = nil) {
9
9
  super.init(appContext: appContext)
10
10
  videoView = VideoView()
11
- videoView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
12
- videoView?.clipsToBounds = true
13
- addSubview(videoView!)
11
+ videoView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
12
+ videoView.clipsToBounds = true
14
13
  }
15
14
 
16
15
  private func trySetLocalCameraTrack() {
@@ -29,16 +28,18 @@ class VideoPreviewView: ExpoView, LocalCameraTrackChangedListener {
29
28
  track is LocalCameraTrack
30
29
  })?.value as? LocalCameraTrack
31
30
  self.localVideoTrack?.start()
32
- self.videoView?.track = self.localVideoTrack
31
+ self.videoView.track = self.localVideoTrack
33
32
  }
34
33
  }
35
34
 
36
35
  override func willMove(toSuperview newSuperview: UIView?) {
37
36
  super.willMove(toSuperview: newSuperview)
38
37
  if newSuperview == nil {
38
+ videoView.removeFromSuperview()
39
39
  RNFishjamClient.localCameraTracksChangedListenersManager.remove(self)
40
40
  localVideoTrack?.stop()
41
41
  } else {
42
+ addSubview(videoView)
42
43
  RNFishjamClient.localCameraTracksChangedListenersManager.add(self)
43
44
  trySetLocalCameraTrack()
44
45
  }
@@ -48,26 +49,25 @@ class VideoPreviewView: ExpoView, LocalCameraTrackChangedListener {
48
49
  didSet {
49
50
  switch videoLayout {
50
51
  case "FIT":
51
- self.videoView?.layout = .fit
52
+ videoView.layout = .fit
52
53
  case "FILL":
53
- self.videoView?.layout = .fill
54
+ videoView.layout = .fill
54
55
  default:
55
- self.videoView?.layout = .fill
56
+ videoView.layout = .fill
56
57
  }
57
58
  }
58
59
  }
59
60
 
60
61
  var mirrorVideo: Bool = false {
61
62
  didSet {
62
- self.videoView?.mirror = mirrorVideo
63
+ videoView.mirror = mirrorVideo
63
64
  }
64
65
  }
65
66
 
66
67
  var captureDeviceId: String? = nil {
67
68
  didSet {
68
- if let captureDeviceId = captureDeviceId {
69
- localVideoTrack?.switchCamera(deviceId: captureDeviceId)
70
- }
69
+ guard let captureDeviceId else { return }
70
+ localVideoTrack?.switchCamera(deviceId: captureDeviceId)
71
71
  }
72
72
  }
73
73
 
@@ -3,42 +3,39 @@ import ExpoModulesCore
3
3
  import FishjamCloudClient
4
4
 
5
5
  class VideoRendererView: ExpoView, TrackUpdateListener {
6
- var videoView: VideoView? = nil
7
- var cancellableEndpoints: Cancellable? = nil
6
+ var videoView: VideoView!
7
+ var cancellableEndpoints: Cancellable?
8
8
 
9
9
  required init(appContext: AppContext? = nil) {
10
10
  super.init(appContext: appContext)
11
- RNFishjamClient.tracksUpdateListenersManager.add(self)
12
11
  videoView = VideoView()
13
- videoView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
14
- videoView?.clipsToBounds = true
15
- addSubview(videoView!)
16
- }
17
-
18
- deinit {
19
- RNFishjamClient.tracksUpdateListenersManager.remove(self)
12
+ videoView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
13
+ videoView.clipsToBounds = true
20
14
  }
21
15
 
22
16
  override func willMove(toSuperview newSuperview: UIView?) {
23
17
  super.willMove(toSuperview: newSuperview)
24
18
 
25
- if newSuperview != nil {
19
+ if newSuperview == nil {
20
+ videoView.removeFromSuperview()
21
+ RNFishjamClient.tracksUpdateListenersManager.remove(self)
22
+ } else {
23
+ addSubview(videoView)
24
+ RNFishjamClient.tracksUpdateListenersManager.add(self)
26
25
  updateVideoTrack()
27
26
  }
28
27
  }
29
28
 
30
29
  func updateVideoTrack() {
31
- DispatchQueue.main.async {
32
- if self.superview != nil {
33
- for endpoint in RNFishjamClient.getLocalAndRemoteEndpoints() {
34
- if let track = endpoint.tracks[self.trackId] as? VideoTrack {
35
- if let track = track as? LocalCameraTrack {
36
- self.mirrorVideo = track.isFrontCamera
37
- }
38
- self.videoView?.track = track
39
- return
40
- }
30
+ DispatchQueue.main.async { [weak self] in
31
+ guard let self, self.superview != nil else { return }
32
+ for endpoint in RNFishjamClient.getLocalAndRemoteEndpoints() {
33
+ guard let track = endpoint.tracks[self.trackId] as? VideoTrack else { continue }
34
+ if let track = track as? LocalCameraTrack {
35
+ self.mirrorVideo = track.isFrontCamera
41
36
  }
37
+ self.videoView.track = track
38
+ return
42
39
  }
43
40
  }
44
41
  }
@@ -57,11 +54,11 @@ class VideoRendererView: ExpoView, TrackUpdateListener {
57
54
  didSet {
58
55
  switch videoLayout {
59
56
  case "FIT":
60
- self.videoView?.layout = .fit
57
+ videoView.layout = .fit
61
58
  case "FILL":
62
- self.videoView?.layout = .fill
59
+ videoView.layout = .fill
63
60
  default:
64
- self.videoView?.layout = .fill
61
+ videoView.layout = .fill
65
62
  }
66
63
 
67
64
  }
@@ -69,7 +66,13 @@ class VideoRendererView: ExpoView, TrackUpdateListener {
69
66
 
70
67
  var mirrorVideo: Bool = false {
71
68
  didSet {
72
- self.videoView?.mirror = mirrorVideo
69
+ videoView.mirror = mirrorVideo
70
+ }
71
+ }
72
+
73
+ var checkVisibilityTimeInterval: TimeInterval? {
74
+ didSet {
75
+ videoView.checkVisibilityTimeInterval = checkVisibilityTimeInterval
73
76
  }
74
77
  }
75
78
  }
@@ -5,12 +5,16 @@ public class VideoRendererViewModule: Module {
5
5
  Name("VideoRendererViewModule")
6
6
 
7
7
  View(VideoRendererView.self) {
8
- Prop("trackId") { (view: VideoRendererView, prop: String) in
9
- view.trackId = prop
8
+ Prop("trackId") { (view, trackId) in
9
+ view.trackId = trackId
10
10
  }
11
11
 
12
- Prop("videoLayout") { (view: VideoRendererView, prop: String) in
13
- view.videoLayout = prop
12
+ Prop("videoLayout") { (view, videoLayout) in
13
+ view.videoLayout = videoLayout
14
+ }
15
+
16
+ Prop("skipRenderOutsideVisibleArea") { (view, skipRenderOutsideVisibleArea) in
17
+ view.checkVisibilityTimeInterval = skipRenderOutsideVisibleArea ? 1 : nil
14
18
  }
15
19
  }
16
20
  }
@@ -0,0 +1,20 @@
1
+ import AVKit
2
+ import FishjamCloudClient
3
+
4
+ public typealias LocalCamera = [String: Any]
5
+
6
+ extension AVCaptureDevice {
7
+ func toLocalCamera() -> LocalCamera {
8
+ let facingDirection =
9
+ switch position {
10
+ case .front: "front"
11
+ case .back: "back"
12
+ default: "unspecified"
13
+ }
14
+ return [
15
+ "id": uniqueID,
16
+ "name": localizedName,
17
+ "facingDirection": facingDirection,
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,23 @@
1
+ import Foundation
2
+
3
+ protocol LocalCameraTrackChangedListener: AnyObject {
4
+ func onLocalCameraTrackChanged()
5
+ }
6
+
7
+ class LocalCameraTracksChangedListenersManager {
8
+ private var listeners: [LocalCameraTrackChangedListener] = []
9
+
10
+ func add(_ listener: LocalCameraTrackChangedListener) {
11
+ listeners.append(listener)
12
+ }
13
+
14
+ func remove(_ listener: LocalCameraTrackChangedListener) {
15
+ listeners.removeAll { $0 === listener }
16
+ }
17
+
18
+ func notifyListeners() {
19
+ for listener in listeners {
20
+ listener.onLocalCameraTrackChanged()
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,21 @@
1
+ protocol TrackUpdateListener: AnyObject {
2
+ func onTracksUpdate()
3
+ }
4
+
5
+ class TracksUpdateListenersManager {
6
+ private var listeners: [TrackUpdateListener] = []
7
+
8
+ func add(_ listener: TrackUpdateListener) {
9
+ listeners.append(listener)
10
+ }
11
+
12
+ func remove(_ listener: TrackUpdateListener) {
13
+ listeners.removeAll { $0 === listener }
14
+ }
15
+
16
+ func notifyListeners() {
17
+ for listener in listeners {
18
+ listener.onTracksUpdate()
19
+ }
20
+ }
21
+ }