@fishjam-cloud/react-native-client 0.1.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 (138) hide show
  1. package/.eslintignore +7 -0
  2. package/.eslintrc +6 -0
  3. package/.prettierrc +9 -0
  4. package/.watchmanconfig +1 -0
  5. package/android/.editorconfig +9 -0
  6. package/android/build.gradle +89 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/org/membraneframework/reactnative/AudioDeviceKind.kt +34 -0
  9. package/android/src/main/java/org/membraneframework/reactnative/AudioSwitchManager.kt +61 -0
  10. package/android/src/main/java/org/membraneframework/reactnative/EmitableEvents.kt +14 -0
  11. package/android/src/main/java/org/membraneframework/reactnative/Errors.kt +31 -0
  12. package/android/src/main/java/org/membraneframework/reactnative/RNFishjamClient.kt +866 -0
  13. package/android/src/main/java/org/membraneframework/reactnative/RNFishjamClientModule.kt +294 -0
  14. package/android/src/main/java/org/membraneframework/reactnative/Utils.kt +11 -0
  15. package/android/src/main/java/org/membraneframework/reactnative/VideoPreviewView.kt +40 -0
  16. package/android/src/main/java/org/membraneframework/reactnative/VideoPreviewViewModule.kt +17 -0
  17. package/android/src/main/java/org/membraneframework/reactnative/VideoRendererView.kt +58 -0
  18. package/android/src/main/java/org/membraneframework/reactnative/VideoRendererViewModule.kt +25 -0
  19. package/android/src/main/java/org/membraneframework/reactnative/VideoView.kt +77 -0
  20. package/app.plugin.js +1 -0
  21. package/babel.config.js +2 -0
  22. package/build/RNFishjamClientModule.d.ts +47 -0
  23. package/build/RNFishjamClientModule.d.ts.map +1 -0
  24. package/build/RNFishjamClientModule.js +4 -0
  25. package/build/RNFishjamClientModule.js.map +1 -0
  26. package/build/common/client.d.ts +19 -0
  27. package/build/common/client.d.ts.map +1 -0
  28. package/build/common/client.js +8 -0
  29. package/build/common/client.js.map +1 -0
  30. package/build/common/eventEmitter.d.ts +16 -0
  31. package/build/common/eventEmitter.d.ts.map +1 -0
  32. package/build/common/eventEmitter.js +17 -0
  33. package/build/common/eventEmitter.js.map +1 -0
  34. package/build/common/metadata.d.ts +7 -0
  35. package/build/common/metadata.d.ts.map +1 -0
  36. package/build/common/metadata.js +9 -0
  37. package/build/common/metadata.js.map +1 -0
  38. package/build/common/webRTC.d.ts +19 -0
  39. package/build/common/webRTC.d.ts.map +1 -0
  40. package/build/common/webRTC.js +22 -0
  41. package/build/common/webRTC.js.map +1 -0
  42. package/build/components/VideoPreviewView.d.ts +21 -0
  43. package/build/components/VideoPreviewView.d.ts.map +1 -0
  44. package/build/components/VideoPreviewView.js +7 -0
  45. package/build/components/VideoPreviewView.js.map +1 -0
  46. package/build/components/VideoRendererView.d.ts +19 -0
  47. package/build/components/VideoRendererView.d.ts.map +1 -0
  48. package/build/components/VideoRendererView.js +7 -0
  49. package/build/components/VideoRendererView.js.map +1 -0
  50. package/build/hooks/useAudioSettings.d.ts +23 -0
  51. package/build/hooks/useAudioSettings.d.ts.map +1 -0
  52. package/build/hooks/useAudioSettings.js +72 -0
  53. package/build/hooks/useAudioSettings.js.map +1 -0
  54. package/build/hooks/useBandwidthEstimation.d.ts +12 -0
  55. package/build/hooks/useBandwidthEstimation.d.ts.map +1 -0
  56. package/build/hooks/useBandwidthEstimation.js +16 -0
  57. package/build/hooks/useBandwidthEstimation.js.map +1 -0
  58. package/build/hooks/useCamera.d.ts +80 -0
  59. package/build/hooks/useCamera.d.ts.map +1 -0
  60. package/build/hooks/useCamera.js +140 -0
  61. package/build/hooks/useCamera.js.map +1 -0
  62. package/build/hooks/useMicrophone.d.ts +8 -0
  63. package/build/hooks/useMicrophone.d.ts.map +1 -0
  64. package/build/hooks/useMicrophone.js +27 -0
  65. package/build/hooks/useMicrophone.js.map +1 -0
  66. package/build/hooks/useParticipants.d.ts +62 -0
  67. package/build/hooks/useParticipants.d.ts.map +1 -0
  68. package/build/hooks/useParticipants.js +32 -0
  69. package/build/hooks/useParticipants.js.map +1 -0
  70. package/build/hooks/useReconnection.d.ts +5 -0
  71. package/build/hooks/useReconnection.d.ts.map +1 -0
  72. package/build/hooks/useReconnection.js +23 -0
  73. package/build/hooks/useReconnection.js.map +1 -0
  74. package/build/hooks/useScreencast.d.ts +38 -0
  75. package/build/hooks/useScreencast.d.ts.map +1 -0
  76. package/build/hooks/useScreencast.js +81 -0
  77. package/build/hooks/useScreencast.js.map +1 -0
  78. package/build/index.d.ts +24 -0
  79. package/build/index.d.ts.map +1 -0
  80. package/build/index.js +14 -0
  81. package/build/index.js.map +1 -0
  82. package/build/protos/fishjam/peer_notifications.d.ts +63 -0
  83. package/build/protos/fishjam/peer_notifications.d.ts.map +1 -0
  84. package/build/protos/fishjam/peer_notifications.js +235 -0
  85. package/build/protos/fishjam/peer_notifications.js.map +1 -0
  86. package/build/stats/types.d.ts +55 -0
  87. package/build/stats/types.d.ts.map +1 -0
  88. package/build/stats/types.js +2 -0
  89. package/build/stats/types.js.map +1 -0
  90. package/build/stats/useRTCStatistics.d.ts +8 -0
  91. package/build/stats/useRTCStatistics.d.ts.map +1 -0
  92. package/build/stats/useRTCStatistics.js +78 -0
  93. package/build/stats/useRTCStatistics.js.map +1 -0
  94. package/build/types.d.ts +43 -0
  95. package/build/types.d.ts.map +1 -0
  96. package/build/types.js +2 -0
  97. package/build/types.js.map +1 -0
  98. package/expo-module.config.json +17 -0
  99. package/ios/Events.swift +13 -0
  100. package/ios/RNFishjamClient.podspec +30 -0
  101. package/ios/RNFishjamClient.swift +836 -0
  102. package/ios/RNFishjamClientModule.swift +220 -0
  103. package/ios/Utils.swift +43 -0
  104. package/ios/VideoPreviewView.swift +64 -0
  105. package/ios/VideoPreviewViewModule.swift +21 -0
  106. package/ios/VideoRendererView.swift +84 -0
  107. package/ios/VideoRendererViewModule.swift +17 -0
  108. package/ios/swift-format-config.json +56 -0
  109. package/package.json +89 -0
  110. package/plugin/broadcastExtensionFiles/Info.plist +27 -0
  111. package/plugin/broadcastExtensionFiles/MembraneBroadcastSampleHandler.swift +50 -0
  112. package/plugin/broadcastExtensionFiles/MembraneScreenBroadcastExtension.entitlements +10 -0
  113. package/plugin/src/withMembrane.ts +310 -0
  114. package/plugin/tsconfig.json +9 -0
  115. package/scripts/lint.sh +18 -0
  116. package/scripts/lint_check.sh +14 -0
  117. package/src/RNFishjamClientModule.ts +73 -0
  118. package/src/__tests__/index.test.tsx +199 -0
  119. package/src/common/client.ts +33 -0
  120. package/src/common/eventEmitter.ts +21 -0
  121. package/src/common/metadata.ts +12 -0
  122. package/src/common/webRTC.ts +31 -0
  123. package/src/components/VideoPreviewView.tsx +33 -0
  124. package/src/components/VideoRendererView.tsx +31 -0
  125. package/src/hooks/useAudioSettings.ts +115 -0
  126. package/src/hooks/useBandwidthEstimation.ts +24 -0
  127. package/src/hooks/useCamera.ts +270 -0
  128. package/src/hooks/useMicrophone.ts +38 -0
  129. package/src/hooks/useParticipants.ts +118 -0
  130. package/src/hooks/useReconnection.ts +41 -0
  131. package/src/hooks/useScreencast.ts +146 -0
  132. package/src/index.tsx +63 -0
  133. package/src/protos/fishjam/peer_notifications.ts +299 -0
  134. package/src/stats/types.ts +56 -0
  135. package/src/stats/useRTCStatistics.ts +97 -0
  136. package/src/types.ts +49 -0
  137. package/tsconfig.json +9 -0
  138. package/typedoc.json +5 -0
@@ -0,0 +1,836 @@
1
+ import AVKit
2
+ import ExpoModulesCore
3
+ import FishjamCloudClient
4
+ import React
5
+ import ReplayKit
6
+ import WebRTC
7
+
8
+ class RNFishjamClient: FishjamClientListener {
9
+ static var fishjamClient: FishjamClient? = nil
10
+
11
+ var isMicrophoneOn = false
12
+ var isCameraOn = false
13
+ var isScreencastOn = false
14
+ var isConnected = false
15
+
16
+ var connectPromise: Promise? = nil
17
+
18
+ var videoSimulcastConfig: SimulcastConfig = SimulcastConfig()
19
+ var localUserMetadata: Metadata = .init()
20
+
21
+ var screencastSimulcastConfig: SimulcastConfig = SimulcastConfig()
22
+ var screencastMaxBandwidth: TrackBandwidthLimit = .BandwidthLimit(0)
23
+
24
+ var captureDeviceId: String? = nil
25
+
26
+ var audioSessionMode: AVAudioSession.Mode = AVAudioSession.Mode.videoChat
27
+ var errorMessage: String?
28
+
29
+ let sendEvent: (_ eventName: String, _ data: [String: Any]) -> Void
30
+
31
+ static var onTracksUpdateListeners: [OnTrackUpdateListener] = []
32
+
33
+ init(sendEvent: @escaping (_ eventName: String, _ data: [String: Any]) -> Void) {
34
+ self.sendEvent = sendEvent
35
+ NotificationCenter.default.addObserver(
36
+ self,
37
+ selector: #selector(onRouteChangeNotification),
38
+ name: AVAudioSession.routeChangeNotification,
39
+ object: nil
40
+ )
41
+ }
42
+
43
+ private func getSimulcastConfigFromOptions(simulcastConfig: RNSimulcastConfig) throws -> SimulcastConfig {
44
+ var activeEncodings: [TrackEncoding] = []
45
+
46
+ for encoding in simulcastConfig.activeEncodings {
47
+ if let fishjamEncoding = try? TrackEncoding(encoding) {
48
+ activeEncodings.append(fishjamEncoding)
49
+ }
50
+ }
51
+
52
+ return SimulcastConfig(
53
+ enabled: simulcastConfig.enabled,
54
+ activeEncodings: activeEncodings
55
+ )
56
+ }
57
+
58
+ private func getMaxBandwidthFromOptions(maxBandwidth: RNTrackBandwidthLimit) -> TrackBandwidthLimit {
59
+ if let maxBandwidth: Int = maxBandwidth.get() {
60
+ return .BandwidthLimit(maxBandwidth)
61
+ } else if let maxBandwidth: [String: Int] = maxBandwidth.get() {
62
+ return .SimulcastBandwidthLimit(maxBandwidth)
63
+ }
64
+ return .BandwidthLimit(0)
65
+ }
66
+
67
+ func create() {
68
+ RNFishjamClient.fishjamClient = FishjamClient(listener: self)
69
+ }
70
+
71
+ func getVideoParametersFromOptions(connectionOptions: CameraConfig) throws -> VideoParameters {
72
+ let videoQuality = connectionOptions.quality
73
+ let flipVideo = connectionOptions.flipVideo
74
+ let videoBandwidthLimit = getMaxBandwidthFromOptions(maxBandwidth: connectionOptions.maxBandwidth)
75
+ let simulcastConfig = try getSimulcastConfigFromOptions(simulcastConfig: connectionOptions.simulcastConfig)
76
+
77
+ let preset: VideoParameters = {
78
+ switch videoQuality {
79
+ case "QVGA169":
80
+ return VideoParameters.presetQVGA169
81
+ case "VGA169":
82
+ return VideoParameters.presetVGA169
83
+ case "VQHD169":
84
+ return VideoParameters.presetQHD169
85
+ case "HD169":
86
+ return VideoParameters.presetHD169
87
+ case "FHD169":
88
+ return VideoParameters.presetFHD169
89
+ case "QVGA43":
90
+ return VideoParameters.presetQVGA43
91
+ case "VGA43":
92
+ return VideoParameters.presetVGA43
93
+ case "VQHD43":
94
+ return VideoParameters.presetQHD43
95
+ case "HD43":
96
+ return VideoParameters.presetHD43
97
+ case "FHD43":
98
+ return VideoParameters.presetFHD43
99
+ default:
100
+ return VideoParameters.presetVGA169
101
+ }
102
+ }()
103
+ let videoParameters = VideoParameters(
104
+ dimensions: flipVideo ? preset.dimensions.flip() : preset.dimensions,
105
+ maxBandwidth: videoBandwidthLimit,
106
+ simulcastConfig: simulcastConfig
107
+ )
108
+ return videoParameters
109
+ }
110
+
111
+ private func getLocalVideoTrack() -> LocalVideoTrack? {
112
+ return RNFishjamClient.fishjamClient?.getLocalEndpoint().tracks.first { $0.value is LocalVideoTrack }?.value
113
+ as? LocalVideoTrack
114
+ }
115
+
116
+ private func getLocalAudioTrack() -> LocalAudioTrack? {
117
+ return RNFishjamClient.fishjamClient?.getLocalEndpoint().tracks.first { $0.value is LocalAudioTrack }?.value
118
+ as? LocalAudioTrack
119
+ }
120
+
121
+ private func getLocalScreencastTrack() -> LocalScreencastTrack? {
122
+ return RNFishjamClient.fishjamClient?.getLocalEndpoint().tracks.first { $0.value is LocalScreencastTrack }?
123
+ .value as? LocalScreencastTrack
124
+ }
125
+
126
+ private func ensureCreated() throws {
127
+ if RNFishjamClient.fishjamClient == nil {
128
+ throw Exception(
129
+ name: "E_NOT_CREATED",
130
+ description: "Client not created. Call create"
131
+ )
132
+ }
133
+ }
134
+
135
+ private func ensureConnected() throws {
136
+ if !isConnected {
137
+ throw Exception(
138
+ name: "E_NOT_CONNECTED",
139
+ description:
140
+ "Client not connected to server yet. Make sure to call connect() first!")
141
+ }
142
+ }
143
+
144
+ private func ensureVideoTrack() throws {
145
+ if getLocalVideoTrack() == nil {
146
+ throw Exception(
147
+ name: "E_NO_LOCAL_VIDEO_TRACK",
148
+ description: "No local video track. Make sure to call connect() first!")
149
+ }
150
+ }
151
+
152
+ private func ensureAudioTrack() throws {
153
+ if getLocalAudioTrack() == nil {
154
+ throw Exception(
155
+ name: "E_NO_LOCAL_AUDIO_TRACK",
156
+ description: "No local audio track. Make sure to call connect() first!")
157
+ }
158
+ }
159
+
160
+ private func ensureScreencastTrack() throws {
161
+ if getLocalScreencastTrack() == nil {
162
+ throw Exception(
163
+ name: "E_NO_LOCAL_SCREENCAST_TRACK",
164
+ description: "No local screencast track. Make sure to toggle screencast on first!")
165
+ }
166
+ }
167
+
168
+ func onAuthError(reason: FishjamCloudClient.AuthError) {
169
+ if let connectPromise = connectPromise {
170
+ connectPromise.reject("E_MEMBRANE_CONNECT", "Failed to connect: socket error")
171
+ }
172
+ connectPromise = nil
173
+ }
174
+
175
+ func onAuthSuccess() {
176
+ joinRoom()
177
+ }
178
+
179
+ func connect(url: String, peerToken: String, peerMetadata: [String: Any], config: ConnectConfig, promise: Promise) {
180
+ connectPromise = promise
181
+ localUserMetadata = peerMetadata.toMetadata()
182
+
183
+ let reconnectConfig = FishjamCloudClient.ReconnectConfig(
184
+ maxAttempts: config.reconnectConfig.maxAttempts, initialDelayMs: config.reconnectConfig.initialDelayMs,
185
+ delayMs: config.reconnectConfig.delayMs)
186
+
187
+ RNFishjamClient.fishjamClient?.connect(
188
+ config: FishjamCloudClient.ConnectConfig(
189
+ websocketUrl: url, token: peerToken, peerMetadata: .init(peerMetadata), reconnectConfig: reconnectConfig
190
+ ))
191
+
192
+ }
193
+
194
+ func joinRoom() {
195
+ RNFishjamClient.fishjamClient?.join(peerMetadata: localUserMetadata)
196
+ }
197
+
198
+ func leaveRoom() {
199
+ if isScreencastOn {
200
+ let screencastExtensionBundleId =
201
+ Bundle.main.infoDictionary?["ScreencastExtensionBundleId"] as? String
202
+ DispatchQueue.main.async {
203
+ RPSystemBroadcastPickerView.show(for: screencastExtensionBundleId)
204
+ }
205
+ }
206
+ isMicrophoneOn = false
207
+ isCameraOn = false
208
+ isScreencastOn = false
209
+ isConnected = false
210
+ RNFishjamClient.fishjamClient?.leave()
211
+ }
212
+
213
+ func startCamera(config: CameraConfig) throws {
214
+ try ensureCreated()
215
+ let cameraTrack = try createCameraTrack(config: config)
216
+ setCameraTrackState(cameraTrack, enabled: config.cameraEnabled)
217
+ emitEndpoints()
218
+ }
219
+
220
+ private func createCameraTrack(config: CameraConfig) throws -> LocalVideoTrack {
221
+ try ensureCreated()
222
+ let videoParameters = try getVideoParametersFromOptions(connectionOptions: config)
223
+ videoSimulcastConfig = try getSimulcastConfigFromOptions(simulcastConfig: config.simulcastConfig)
224
+ return RNFishjamClient.fishjamClient!.createVideoTrack(
225
+ videoParameters: videoParameters,
226
+ metadata: config.videoTrackMetadata.toMetadata(),
227
+ captureDeviceName: config.captureDeviceId
228
+ )
229
+ }
230
+
231
+ private func setCameraTrackState(_ cameraTrack: LocalVideoTrack, enabled: Bool) {
232
+ cameraTrack.enabled = enabled
233
+ isCameraOn = enabled
234
+ let eventName = EmitableEvents.IsCameraOn
235
+ let isCameraEnabledMap = [eventName: enabled]
236
+ emitEvent(name: eventName, data: isCameraEnabledMap)
237
+ }
238
+
239
+ func toggleCamera() throws {
240
+ try ensureVideoTrack()
241
+ setCameraTrackState(getLocalVideoTrack()!, enabled: !isCameraOn)
242
+ }
243
+
244
+ func flipCamera() throws {
245
+ try ensureVideoTrack()
246
+ getLocalVideoTrack()?.flipCamera()
247
+ }
248
+
249
+ func switchCamera(captureDeviceId: String) throws {
250
+ try ensureVideoTrack()
251
+ getLocalVideoTrack()?.switchCamera(deviceId: captureDeviceId)
252
+
253
+ }
254
+
255
+ func toggleMicrophone() throws -> Bool {
256
+ if let audioTrack = getLocalAudioTrack() {
257
+ setMicrophoneTrackState(audioTrack, enabled: !isMicrophoneOn)
258
+ } else {
259
+ try startMicrophone()
260
+ }
261
+ return isMicrophoneOn
262
+ }
263
+
264
+ func startMicrophone() throws {
265
+ let microphoneTrack = RNFishjamClient.fishjamClient!.createAudioTrack(
266
+ metadata: Metadata())
267
+ setAudioSessionMode()
268
+ setMicrophoneTrackState(microphoneTrack, enabled: true)
269
+ emitEndpoints()
270
+ }
271
+
272
+ private func setMicrophoneTrackState(_ microphoneTrack: LocalAudioTrack, enabled: Bool) {
273
+ microphoneTrack.enabled = enabled
274
+ isMicrophoneOn = enabled
275
+ let eventName = EmitableEvents.IsMicrophoneOn
276
+ let isMicrophoneOnMap = [eventName: enabled]
277
+ print(isMicrophoneOnMap)
278
+ emitEvent(name: eventName, data: isMicrophoneOnMap)
279
+ }
280
+
281
+ func setAudioSessionMode() {
282
+ guard let localAudioTrack = getLocalAudioTrack() else {
283
+ return
284
+ }
285
+
286
+ switch self.audioSessionMode {
287
+ case AVAudioSession.Mode.videoChat:
288
+ localAudioTrack.setVideoChatMode()
289
+ break
290
+ case AVAudioSession.Mode.voiceChat:
291
+ localAudioTrack.setVoiceChatMode()
292
+ break
293
+ default:
294
+ localAudioTrack.setVideoChatMode()
295
+ break
296
+ }
297
+ }
298
+
299
+ func toggleScreencast(screencastOptions: ScreencastOptions) throws {
300
+ try ensureCreated()
301
+ try ensureConnected()
302
+ guard let screencastExtensionBundleId = Bundle.main.infoDictionary?["ScreencastExtensionBundleId"] as? String
303
+ else {
304
+ throw Exception(
305
+ name: "E_NO_BUNDLE_ID_SET",
306
+ description:
307
+ "No screencast extension bundle id set. Please set ScreencastExtensionBundleId in Info.plist"
308
+ )
309
+ }
310
+ guard let appGroupName = Bundle.main.infoDictionary?["AppGroupName"] as? String
311
+ else {
312
+ throw Exception(
313
+ name: "E_NO_APP_GROUP_SET",
314
+ description: "No app group name set. Please set AppGroupName in Info.plist")
315
+ }
316
+
317
+ guard !isScreencastOn
318
+ else {
319
+ DispatchQueue.main.async {
320
+ RPSystemBroadcastPickerView.show(for: screencastExtensionBundleId)
321
+ }
322
+ return
323
+ }
324
+
325
+ let simulcastConfig = try getSimulcastConfigFromOptions(simulcastConfig: screencastOptions.simulcastConfig)
326
+
327
+ screencastSimulcastConfig = simulcastConfig
328
+ screencastMaxBandwidth = getMaxBandwidthFromOptions(
329
+ maxBandwidth: screencastOptions.maxBandwidth)
330
+ let screencastMetadata = screencastOptions.screencastMetadata.toMetadata()
331
+ let videoParameters = getScreencastVideoParameters(options: screencastOptions)
332
+ RNFishjamClient.fishjamClient!.createScreencastTrack(
333
+ appGroup: appGroupName,
334
+ videoParameters: videoParameters,
335
+ metadata: screencastMetadata,
336
+ onStart: { [weak self] screencastTrack in
337
+ guard let self = self else {
338
+ DispatchQueue.main.async {
339
+ RPSystemBroadcastPickerView.show(for: screencastExtensionBundleId)
340
+ }
341
+ return
342
+ }
343
+ do {
344
+ //not sure should it be here, or outside or where?
345
+ try setScreencastTrackState(screencastTrack, enabled: true)
346
+ } catch {
347
+ os_log(
348
+ "Error starting screencast: %{public}s", log: log, type: .error,
349
+ String(describing: error)
350
+ )
351
+ }
352
+
353
+ },
354
+ onStop: { [weak self] screencastTrack in
355
+ guard let self = self else {
356
+ return
357
+ }
358
+ do {
359
+ //not sure should it be here, or outside or where?
360
+ try setScreencastTrackState(screencastTrack, enabled: false)
361
+ } catch {
362
+ os_log(
363
+ "Error stopping screencast: %{public}s", log: log, type: .error,
364
+ String(describing: error)
365
+ )
366
+ }
367
+ }
368
+ )
369
+ DispatchQueue.main.async {
370
+ RPSystemBroadcastPickerView.show(for: screencastExtensionBundleId)
371
+ }
372
+ }
373
+
374
+ private func setScreencastTrackState(_ screencastTrack: LocalScreencastTrack, enabled: Bool) throws {
375
+ //was not present before, test and maybe delete?
376
+ screencastTrack.enabled = enabled
377
+ isScreencastOn = enabled
378
+ let eventName = EmitableEvents.IsScreencastOn
379
+ let isScreencastEnabled = [eventName: enabled]
380
+ emitEvent(name: eventName, data: isScreencastEnabled)
381
+ }
382
+
383
+ //returns local endpoint and remote endpoints
384
+ static func getLocalAndRemoteEndpoints() -> [Endpoint] {
385
+ let localEndpoint = RNFishjamClient.fishjamClient!.getLocalEndpoint()
386
+ let remoteEndpoints = RNFishjamClient.fishjamClient!.getRemoteEndpoints()
387
+ return [localEndpoint] + remoteEndpoints
388
+ }
389
+
390
+ func getPeers() throws -> [[String: Any?]] {
391
+ let endpoints = RNFishjamClient.getLocalAndRemoteEndpoints()
392
+ return endpoints.compactMap { endpoint in
393
+ [
394
+ "id": endpoint.id,
395
+ "isLocal": endpoint.id == RNFishjamClient.fishjamClient!.getLocalEndpoint().id,
396
+ "type": endpoint.type,
397
+ "metadata": endpoint.metadata,
398
+ "tracks": endpoint.tracks.values.compactMap { track -> [String: Any?]? in
399
+ switch track {
400
+ case let track as RemoteVideoTrack:
401
+ return [
402
+ "id": track.id,
403
+ "type": "Video",
404
+ "metadata": track.metadata,
405
+ "encoding": track.encoding?.description,
406
+ "encodingReason": track.encodingReason?.rawValue,
407
+ ]
408
+
409
+ case let track as RemoteAudioTrack:
410
+ return [
411
+ "id": track.id,
412
+ "type": "Audio",
413
+ "metadata": track.metadata,
414
+ "vadStatus": track.vadStatus.rawValue,
415
+ ]
416
+
417
+ case let track as LocalVideoTrack:
418
+ return [
419
+ "id": track.id,
420
+ "type": "Video",
421
+ "metadata": track.metadata,
422
+ ]
423
+
424
+ case let track as LocalScreencastTrack:
425
+ return [
426
+ "id": track.id,
427
+ "type": "Video",
428
+ "metadata": track.metadata,
429
+ ]
430
+
431
+ case let track as LocalAudioTrack:
432
+ return [
433
+ "id": track.id,
434
+ "type": "Audio",
435
+ "metadata": track.metadata,
436
+ ]
437
+
438
+ default:
439
+ return nil
440
+ }
441
+ },
442
+ ]
443
+ }
444
+ }
445
+
446
+ func getCaptureDevices() -> [[String: Any]] {
447
+ let devices = LocalVideoTrack.getCaptureDevices()
448
+ return devices.map { device -> [String: Any] in
449
+ return [
450
+ "id": device.uniqueID,
451
+ "name": device.localizedName,
452
+ "isFrontFacing": device.position == .front,
453
+ "isBackFacing": device.position == .back,
454
+ ]
455
+ }
456
+ }
457
+
458
+ func updatePeerMetadata(metadata: [String: Any]) throws {
459
+ try ensureConnected()
460
+ RNFishjamClient.fishjamClient?.updatePeerMetadata(metadata: metadata.toMetadata())
461
+ }
462
+
463
+ func updateTrackMetadata(trackId: String, metadata: [String: Any]) {
464
+ RNFishjamClient.fishjamClient?.updateTrackMetadata(trackId: trackId, metadata: metadata.toMetadata())
465
+ emitEndpoints()
466
+ }
467
+
468
+ func updateLocalVideoTrackMetadata(metadata: [String: Any]) throws {
469
+ try ensureVideoTrack()
470
+ if let track = getLocalVideoTrack() {
471
+ updateTrackMetadata(trackId: track.id, metadata: metadata)
472
+ }
473
+ }
474
+
475
+ func updateLocalAudioTrackMetadata(metadata: [String: Any]) throws {
476
+ try ensureAudioTrack()
477
+ if let track = getLocalAudioTrack() {
478
+ updateTrackMetadata(trackId: track.id, metadata: metadata)
479
+ }
480
+ }
481
+
482
+ func updateLocalScreencastTrackMetadata(metadata: [String: Any]) throws {
483
+ try ensureScreencastTrack()
484
+ if let track = getLocalScreencastTrack() {
485
+ updateTrackMetadata(trackId: track.id, metadata: metadata)
486
+ }
487
+ }
488
+
489
+ private func toggleTrackEncoding(encoding: String, trackId: String, simulcastConfig: SimulcastConfig) throws
490
+ -> SimulcastConfig
491
+ {
492
+ try ensureCreated()
493
+ let trackEncoding = try TrackEncoding(encoding)
494
+ let trackEncodingActive = simulcastConfig.activeEncodings.contains(trackEncoding)
495
+ var updatedEncodings = simulcastConfig.activeEncodings
496
+
497
+ if trackEncodingActive {
498
+ RNFishjamClient.fishjamClient?.disableTrackEncoding(trackId: trackId, encoding: trackEncoding)
499
+ updatedEncodings.removeAll(where: { encoding in trackEncoding == encoding })
500
+ } else {
501
+ RNFishjamClient.fishjamClient?.enableTrackEncoding(trackId: trackId, encoding: trackEncoding)
502
+ updatedEncodings += [trackEncoding]
503
+ }
504
+
505
+ return SimulcastConfig(enabled: true, activeEncodings: updatedEncodings)
506
+ }
507
+
508
+ func toggleScreencastTrackEncoding(encoding: String) throws -> [String: Any] {
509
+ try ensureScreencastTrack()
510
+ if let track = getLocalScreencastTrack() {
511
+ screencastSimulcastConfig = try toggleTrackEncoding(
512
+ encoding: encoding, trackId: track.id, simulcastConfig: screencastSimulcastConfig)
513
+ }
514
+ return getSimulcastConfigAsRNMap(screencastSimulcastConfig)
515
+ }
516
+
517
+ func setScreencastTrackBandwidth(bandwidth: Int) throws {
518
+ try ensureScreencastTrack()
519
+ if let track = getLocalScreencastTrack() {
520
+ RNFishjamClient.fishjamClient?.setTrackBandwidth(trackId: track.id, bandwidthLimit: bandwidth)
521
+ }
522
+ }
523
+
524
+ func setScreencastTrackEncodingBandwidth(encoding: String, bandwidth: Int) throws {
525
+ try ensureScreencastTrack()
526
+ if let track = getLocalScreencastTrack() {
527
+ RNFishjamClient.fishjamClient?.setEncodingBandwidth(
528
+ trackId: track.id, encoding: encoding, bandwidthLimit: bandwidth)
529
+ }
530
+ }
531
+
532
+ func setTargetTrackEncoding(trackId: String, encoding: String) throws {
533
+ try ensureConnected()
534
+ let trackEncoding = try TrackEncoding(encoding)
535
+ RNFishjamClient.fishjamClient?.setTargetTrackEncoding(trackId: trackId, encoding: trackEncoding)
536
+ }
537
+
538
+ func toggleVideoTrackEncoding(encoding: String) throws -> [String: Any] {
539
+ try ensureVideoTrack()
540
+ try ensureConnected()
541
+
542
+ let track = getLocalVideoTrack()!
543
+ videoSimulcastConfig = try toggleTrackEncoding(
544
+ encoding: encoding, trackId: track.id, simulcastConfig: videoSimulcastConfig)
545
+
546
+ let eventName = EmitableEvents.SimulcastConfigUpdate
547
+ let simulcastConfigAsRNMap = getSimulcastConfigAsRNMap(videoSimulcastConfig)
548
+ emitEvent(name: eventName, data: simulcastConfigAsRNMap)
549
+
550
+ return simulcastConfigAsRNMap
551
+ }
552
+
553
+ func setVideoTrackEncodingBandwidth(encoding: String, bandwidth: Int) throws {
554
+ try ensureVideoTrack()
555
+ let track = getLocalVideoTrack()!
556
+ RNFishjamClient.fishjamClient?.setEncodingBandwidth(
557
+ trackId: track.id, encoding: encoding, bandwidthLimit: bandwidth)
558
+ }
559
+
560
+ func setVideoTrackBandwidth(bandwidth: Int) throws {
561
+ try ensureVideoTrack()
562
+ let track = getLocalVideoTrack()!
563
+ RNFishjamClient.fishjamClient?.setTrackBandwidth(trackId: track.id, bandwidthLimit: bandwidth)
564
+ }
565
+
566
+ func changeWebRTCLoggingSeverity(severity: String) throws {
567
+ switch severity {
568
+ case "verbose":
569
+ RNFishjamClient.fishjamClient?.changeWebRTCLoggingSeverity(severity: .verbose)
570
+ case "info":
571
+ RNFishjamClient.fishjamClient?.changeWebRTCLoggingSeverity(severity: .info)
572
+ case "warning":
573
+ RNFishjamClient.fishjamClient?.changeWebRTCLoggingSeverity(severity: .warning)
574
+ case "error":
575
+ RNFishjamClient.fishjamClient?.changeWebRTCLoggingSeverity(severity: .error)
576
+ case "none":
577
+ RNFishjamClient.fishjamClient?.changeWebRTCLoggingSeverity(severity: .none)
578
+ default:
579
+ throw Exception(
580
+ name: "E_INVALID_SEVERITY_LEVEL",
581
+ description: "Severity with name=\(severity) not found")
582
+ }
583
+ }
584
+
585
+ private func rtcOutboundStatsToRNMap(obj: RTCOutboundStats) -> [String: Any] {
586
+ var innerMap: [String: Double] = [:]
587
+
588
+ innerMap["bandwidth"] = obj.qualityLimitationDurations?.bandwidth ?? 0.0
589
+ innerMap["cpu"] = obj.qualityLimitationDurations?.cpu ?? 0.0
590
+ innerMap["none"] = obj.qualityLimitationDurations?.none ?? 0.0
591
+ innerMap["other"] = obj.qualityLimitationDurations?.other ?? 0.0
592
+
593
+ var res: [String: Any] = [:]
594
+ res["kind"] = obj.kind
595
+ res["rid"] = obj.rid
596
+ res["bytesSent"] = obj.bytesSent
597
+ res["targetBitrate"] = obj.targetBitrate
598
+ res["packetsSent"] = obj.packetsSent
599
+ res["framesEncoded"] = obj.framesEncoded
600
+ res["framesPerSecond"] = obj.framesPerSecond
601
+ res["frameWidth"] = obj.frameWidth
602
+ res["frameHeight"] = obj.frameHeight
603
+ res["qualityLimitationDurations"] = innerMap
604
+
605
+ return res
606
+ }
607
+
608
+ private func rtcInboundStatsToRNMap(obj: RTCInboundStats) -> [String: Any] {
609
+ var res: [String: Any] = [:]
610
+ res["kind"] = obj.kind
611
+ res["jitter"] = obj.jitter
612
+ res["packetsLost"] = obj.packetsLost
613
+ res["packetsReceived"] = obj.packetsReceived
614
+ res["bytesReceived"] = obj.bytesReceived
615
+ res["framesReceived"] = obj.framesReceived
616
+ res["frameWidth"] = obj.frameWidth
617
+ res["frameHeight"] = obj.frameHeight
618
+ res["framesPerSecond"] = obj.framesPerSecond
619
+ res["framesDropped"] = obj.framesDropped
620
+
621
+ return res
622
+ }
623
+
624
+ func getStatistics() throws -> [String: Any] {
625
+ try ensureCreated()
626
+
627
+ let stats = RNFishjamClient.fishjamClient!.getStats()
628
+ let pairs = stats.map { (key, value) in
629
+ let rnValue =
630
+ value is RTCOutboundStats
631
+ ? rtcOutboundStatsToRNMap(obj: value as! RTCOutboundStats)
632
+ : rtcInboundStatsToRNMap(obj: value as! RTCInboundStats)
633
+ return (key, rnValue)
634
+ }
635
+
636
+ return Dictionary(uniqueKeysWithValues: pairs)
637
+ }
638
+
639
+ private func getScreencastVideoParameters(options: ScreencastOptions) -> VideoParameters {
640
+ let preset: VideoParameters
641
+ switch options.quality {
642
+ case "VGA":
643
+ preset = VideoParameters.presetScreenShareVGA
644
+ case "HD5":
645
+ preset = VideoParameters.presetScreenShareHD5
646
+ case "HD15":
647
+ preset = VideoParameters.presetScreenShareHD15
648
+ case "FHD15":
649
+ preset = VideoParameters.presetScreenShareFHD15
650
+ case "FHD30":
651
+ preset = VideoParameters.presetScreenShareFHD30
652
+ default:
653
+ preset = VideoParameters.presetScreenShareHD15
654
+ }
655
+ return VideoParameters(
656
+ dimensions: preset.dimensions.flip(),
657
+ maxBandwidth: screencastMaxBandwidth,
658
+ maxFps: preset.maxFps,
659
+ simulcastConfig: screencastSimulcastConfig
660
+ )
661
+ }
662
+
663
+ func emitEvent(name: String, data: [String: Any]) {
664
+ sendEvent(name, data)
665
+ }
666
+
667
+ func emitEndpoints() {
668
+ let eventName = EmitableEvents.PeersUpdate
669
+ let EndpointsUpdateMap = [eventName: (try? getPeers()) ?? []]
670
+ emitEvent(name: eventName, data: EndpointsUpdateMap)
671
+ }
672
+
673
+ func onJoined(peerID: String, peersInRoom: [String: Endpoint]) {
674
+ isConnected = true
675
+ connectPromise?.resolve(nil)
676
+ connectPromise = nil
677
+ emitEndpoints()
678
+ }
679
+
680
+ func onJoinError(metadata: Any) {
681
+ connectPromise?.reject("E_MEMBRANE_CONNECT", "Failed to join room")
682
+ connectPromise = nil
683
+ }
684
+
685
+ private func addOrUpdateTrack(_ track: Track) {
686
+ if track is RemoteAudioTrack {
687
+ (track as! RemoteAudioTrack).setVadChangedListener { track in
688
+ self.emitEndpoints()
689
+ }
690
+ }
691
+
692
+ if track is RemoteVideoTrack {
693
+ (track as! RemoteVideoTrack).setOnEncodingChangedListener { track in
694
+ self.emitEndpoints()
695
+ }
696
+ }
697
+
698
+ RNFishjamClient.onTracksUpdateListeners.forEach {
699
+ $0.onTrackUpdate()
700
+ }
701
+
702
+ emitEndpoints()
703
+ }
704
+
705
+ func onTrackReady(track: Track) {
706
+ addOrUpdateTrack(track)
707
+ }
708
+
709
+ func onTrackAdded(track: Track) {}
710
+
711
+ func onTrackRemoved(track: Track) {
712
+ emitEndpoints()
713
+ }
714
+
715
+ func onTrackUpdated(track: Track) {
716
+ emitEndpoints()
717
+ }
718
+
719
+ func onPeerJoined(endpoint: Endpoint) {
720
+ emitEndpoints()
721
+ }
722
+
723
+ func onPeerLeft(endpoint: Endpoint) {
724
+ emitEndpoints()
725
+ }
726
+
727
+ func onPeerUpdated(endpoint: Endpoint) {}
728
+
729
+ func onSocketClose(code: UInt16, reason: String) {
730
+ if let connectPromise = connectPromise {
731
+ connectPromise.reject(
732
+ "E_MEMBRANE_CONNECT", "Failed to connect: socket close, code: \(code), reason: \(reason)")
733
+ }
734
+ connectPromise = nil
735
+ }
736
+
737
+ func onSocketError() {
738
+ if let connectPromise = connectPromise {
739
+ connectPromise.reject("E_MEMBRANE_CONNECT", "Failed to connect: socket error")
740
+ }
741
+ connectPromise = nil
742
+ }
743
+
744
+ func onSocketOpen() {}
745
+
746
+ func onDisconnected() {}
747
+
748
+ func getSimulcastConfigAsRNMap(_ simulcastConfig: SimulcastConfig) -> [String: Any] {
749
+ return [
750
+ "enabled": simulcastConfig.enabled,
751
+ "activeEncodings": simulcastConfig.activeEncodings.map { e in e.description },
752
+ ]
753
+ }
754
+
755
+ func selectAudioSessionMode(sessionMode: String) throws {
756
+ switch sessionMode {
757
+ case "videoChat":
758
+ self.audioSessionMode = AVAudioSession.Mode.videoChat
759
+ break
760
+ case "voiceChat":
761
+ self.audioSessionMode = AVAudioSession.Mode.voiceChat
762
+ break
763
+ default:
764
+ throw Exception(
765
+ name: "E_MEMBRANE_AUDIO_SESSION",
766
+ description:
767
+ "Invalid audio session mode: \(sessionMode). Supported modes: videoChat, voiceChat"
768
+ )
769
+ }
770
+ setAudioSessionMode()
771
+ }
772
+
773
+ func showAudioRoutePicker() {
774
+ DispatchQueue.main.sync {
775
+ let pickerView = AVRoutePickerView()
776
+ if let button = pickerView.subviews.first(where: { $0 is UIButton }) as? UIButton {
777
+ button.sendActions(for: .touchUpInside)
778
+ }
779
+ }
780
+ }
781
+
782
+ func startAudioSwitcher() {
783
+ onRouteChangeNotification()
784
+ }
785
+
786
+ @objc func onRouteChangeNotification() {
787
+ let currentRoute = AVAudioSession.sharedInstance().currentRoute
788
+ let output = currentRoute.outputs[0]
789
+ let deviceType = output.portType
790
+ var deviceTypeString: String = ""
791
+
792
+ switch deviceType {
793
+ case .bluetoothA2DP, .bluetoothLE, .bluetoothHFP:
794
+ deviceTypeString = "bluetooth"
795
+ break
796
+ case .builtInSpeaker:
797
+ deviceTypeString = "speaker"
798
+ break
799
+ case .builtInReceiver:
800
+ deviceTypeString = "earpiece"
801
+ break
802
+ case .headphones:
803
+ deviceTypeString = "headphones"
804
+ break
805
+ default:
806
+ deviceTypeString = deviceType.rawValue
807
+ }
808
+ let eventName = EmitableEvents.AudioDeviceUpdate
809
+ emitEvent(
810
+ name: eventName,
811
+ data: [
812
+ eventName: [
813
+ "selectedDevice": ["name": output.portName, "type": deviceTypeString],
814
+ "availableDevices": [],
815
+ ]
816
+ ] as [String: [String: Any]])
817
+ }
818
+
819
+ func onBandwidthEstimationChanged(estimation: Int) {
820
+ let eventName = EmitableEvents.BandwidthEstimation
821
+ emitEvent(name: eventName, data: [eventName: estimation])
822
+ }
823
+
824
+ func onReconnectionStarted() {
825
+ emitEvent(name: EmitableEvents.ReconnectionStarted, data: [:])
826
+ }
827
+
828
+ func onReconnected() {
829
+ emitEvent(name: EmitableEvents.Reconnected, data: [:])
830
+ }
831
+
832
+ func onReconnectionRetriesLimitReached() {
833
+ emitEvent(name: EmitableEvents.ReconnectionRetriesLimitReached, data: [:])
834
+ }
835
+
836
+ }