@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.
- package/.eslintignore +7 -0
- package/.eslintrc +6 -0
- package/.prettierrc +9 -0
- package/.watchmanconfig +1 -0
- package/android/.editorconfig +9 -0
- package/android/build.gradle +89 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/org/membraneframework/reactnative/AudioDeviceKind.kt +34 -0
- package/android/src/main/java/org/membraneframework/reactnative/AudioSwitchManager.kt +61 -0
- package/android/src/main/java/org/membraneframework/reactnative/EmitableEvents.kt +14 -0
- package/android/src/main/java/org/membraneframework/reactnative/Errors.kt +31 -0
- package/android/src/main/java/org/membraneframework/reactnative/RNFishjamClient.kt +866 -0
- package/android/src/main/java/org/membraneframework/reactnative/RNFishjamClientModule.kt +294 -0
- package/android/src/main/java/org/membraneframework/reactnative/Utils.kt +11 -0
- package/android/src/main/java/org/membraneframework/reactnative/VideoPreviewView.kt +40 -0
- package/android/src/main/java/org/membraneframework/reactnative/VideoPreviewViewModule.kt +17 -0
- package/android/src/main/java/org/membraneframework/reactnative/VideoRendererView.kt +58 -0
- package/android/src/main/java/org/membraneframework/reactnative/VideoRendererViewModule.kt +25 -0
- package/android/src/main/java/org/membraneframework/reactnative/VideoView.kt +77 -0
- package/app.plugin.js +1 -0
- package/babel.config.js +2 -0
- package/build/RNFishjamClientModule.d.ts +47 -0
- package/build/RNFishjamClientModule.d.ts.map +1 -0
- package/build/RNFishjamClientModule.js +4 -0
- package/build/RNFishjamClientModule.js.map +1 -0
- package/build/common/client.d.ts +19 -0
- package/build/common/client.d.ts.map +1 -0
- package/build/common/client.js +8 -0
- package/build/common/client.js.map +1 -0
- package/build/common/eventEmitter.d.ts +16 -0
- package/build/common/eventEmitter.d.ts.map +1 -0
- package/build/common/eventEmitter.js +17 -0
- package/build/common/eventEmitter.js.map +1 -0
- package/build/common/metadata.d.ts +7 -0
- package/build/common/metadata.d.ts.map +1 -0
- package/build/common/metadata.js +9 -0
- package/build/common/metadata.js.map +1 -0
- package/build/common/webRTC.d.ts +19 -0
- package/build/common/webRTC.d.ts.map +1 -0
- package/build/common/webRTC.js +22 -0
- package/build/common/webRTC.js.map +1 -0
- package/build/components/VideoPreviewView.d.ts +21 -0
- package/build/components/VideoPreviewView.d.ts.map +1 -0
- package/build/components/VideoPreviewView.js +7 -0
- package/build/components/VideoPreviewView.js.map +1 -0
- package/build/components/VideoRendererView.d.ts +19 -0
- package/build/components/VideoRendererView.d.ts.map +1 -0
- package/build/components/VideoRendererView.js +7 -0
- package/build/components/VideoRendererView.js.map +1 -0
- package/build/hooks/useAudioSettings.d.ts +23 -0
- package/build/hooks/useAudioSettings.d.ts.map +1 -0
- package/build/hooks/useAudioSettings.js +72 -0
- package/build/hooks/useAudioSettings.js.map +1 -0
- package/build/hooks/useBandwidthEstimation.d.ts +12 -0
- package/build/hooks/useBandwidthEstimation.d.ts.map +1 -0
- package/build/hooks/useBandwidthEstimation.js +16 -0
- package/build/hooks/useBandwidthEstimation.js.map +1 -0
- package/build/hooks/useCamera.d.ts +80 -0
- package/build/hooks/useCamera.d.ts.map +1 -0
- package/build/hooks/useCamera.js +140 -0
- package/build/hooks/useCamera.js.map +1 -0
- package/build/hooks/useMicrophone.d.ts +8 -0
- package/build/hooks/useMicrophone.d.ts.map +1 -0
- package/build/hooks/useMicrophone.js +27 -0
- package/build/hooks/useMicrophone.js.map +1 -0
- package/build/hooks/useParticipants.d.ts +62 -0
- package/build/hooks/useParticipants.d.ts.map +1 -0
- package/build/hooks/useParticipants.js +32 -0
- package/build/hooks/useParticipants.js.map +1 -0
- package/build/hooks/useReconnection.d.ts +5 -0
- package/build/hooks/useReconnection.d.ts.map +1 -0
- package/build/hooks/useReconnection.js +23 -0
- package/build/hooks/useReconnection.js.map +1 -0
- package/build/hooks/useScreencast.d.ts +38 -0
- package/build/hooks/useScreencast.d.ts.map +1 -0
- package/build/hooks/useScreencast.js +81 -0
- package/build/hooks/useScreencast.js.map +1 -0
- package/build/index.d.ts +24 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +14 -0
- package/build/index.js.map +1 -0
- package/build/protos/fishjam/peer_notifications.d.ts +63 -0
- package/build/protos/fishjam/peer_notifications.d.ts.map +1 -0
- package/build/protos/fishjam/peer_notifications.js +235 -0
- package/build/protos/fishjam/peer_notifications.js.map +1 -0
- package/build/stats/types.d.ts +55 -0
- package/build/stats/types.d.ts.map +1 -0
- package/build/stats/types.js +2 -0
- package/build/stats/types.js.map +1 -0
- package/build/stats/useRTCStatistics.d.ts +8 -0
- package/build/stats/useRTCStatistics.d.ts.map +1 -0
- package/build/stats/useRTCStatistics.js +78 -0
- package/build/stats/useRTCStatistics.js.map +1 -0
- package/build/types.d.ts +43 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/expo-module.config.json +17 -0
- package/ios/Events.swift +13 -0
- package/ios/RNFishjamClient.podspec +30 -0
- package/ios/RNFishjamClient.swift +836 -0
- package/ios/RNFishjamClientModule.swift +220 -0
- package/ios/Utils.swift +43 -0
- package/ios/VideoPreviewView.swift +64 -0
- package/ios/VideoPreviewViewModule.swift +21 -0
- package/ios/VideoRendererView.swift +84 -0
- package/ios/VideoRendererViewModule.swift +17 -0
- package/ios/swift-format-config.json +56 -0
- package/package.json +89 -0
- package/plugin/broadcastExtensionFiles/Info.plist +27 -0
- package/plugin/broadcastExtensionFiles/MembraneBroadcastSampleHandler.swift +50 -0
- package/plugin/broadcastExtensionFiles/MembraneScreenBroadcastExtension.entitlements +10 -0
- package/plugin/src/withMembrane.ts +310 -0
- package/plugin/tsconfig.json +9 -0
- package/scripts/lint.sh +18 -0
- package/scripts/lint_check.sh +14 -0
- package/src/RNFishjamClientModule.ts +73 -0
- package/src/__tests__/index.test.tsx +199 -0
- package/src/common/client.ts +33 -0
- package/src/common/eventEmitter.ts +21 -0
- package/src/common/metadata.ts +12 -0
- package/src/common/webRTC.ts +31 -0
- package/src/components/VideoPreviewView.tsx +33 -0
- package/src/components/VideoRendererView.tsx +31 -0
- package/src/hooks/useAudioSettings.ts +115 -0
- package/src/hooks/useBandwidthEstimation.ts +24 -0
- package/src/hooks/useCamera.ts +270 -0
- package/src/hooks/useMicrophone.ts +38 -0
- package/src/hooks/useParticipants.ts +118 -0
- package/src/hooks/useReconnection.ts +41 -0
- package/src/hooks/useScreencast.ts +146 -0
- package/src/index.tsx +63 -0
- package/src/protos/fishjam/peer_notifications.ts +299 -0
- package/src/stats/types.ts +56 -0
- package/src/stats/useRTCStatistics.ts +97 -0
- package/src/types.ts +49 -0
- package/tsconfig.json +9 -0
- package/typedoc.json +5 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
struct RNSimulcastConfig: Record {
|
|
4
|
+
@Field
|
|
5
|
+
var enabled: Bool = false
|
|
6
|
+
|
|
7
|
+
@Field
|
|
8
|
+
var activeEncodings: [String] = []
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct CameraConfig: Record {
|
|
12
|
+
@Field
|
|
13
|
+
var quality: String = "VGA169"
|
|
14
|
+
|
|
15
|
+
@Field
|
|
16
|
+
var flipVideo: Bool = false
|
|
17
|
+
|
|
18
|
+
@Field
|
|
19
|
+
var videoTrackMetadata: [String: Any] = [:]
|
|
20
|
+
|
|
21
|
+
@Field
|
|
22
|
+
var simulcastConfig: RNSimulcastConfig = RNSimulcastConfig()
|
|
23
|
+
|
|
24
|
+
@Field
|
|
25
|
+
var maxBandwidth: RNTrackBandwidthLimit = RNTrackBandwidthLimit(0)
|
|
26
|
+
|
|
27
|
+
@Field
|
|
28
|
+
var cameraEnabled: Bool = true
|
|
29
|
+
|
|
30
|
+
@Field
|
|
31
|
+
var captureDeviceId: String? = nil
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
struct MicrophoneConfig: Record {
|
|
35
|
+
@Field
|
|
36
|
+
var audioTrackMetadata: [String: Any] = [:]
|
|
37
|
+
|
|
38
|
+
@Field
|
|
39
|
+
var microphoneEnabled: Bool = true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
struct ScreencastOptions: Record {
|
|
43
|
+
@Field
|
|
44
|
+
var quality: String = "HD15"
|
|
45
|
+
|
|
46
|
+
@Field
|
|
47
|
+
var screencastMetadata: [String: Any] = [:]
|
|
48
|
+
|
|
49
|
+
@Field
|
|
50
|
+
var simulcastConfig: RNSimulcastConfig = RNSimulcastConfig()
|
|
51
|
+
|
|
52
|
+
@Field
|
|
53
|
+
var maxBandwidth: RNTrackBandwidthLimit = RNTrackBandwidthLimit(0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
struct ReconnectConfig: Record {
|
|
57
|
+
@Field
|
|
58
|
+
var maxAttempts: Int = 5
|
|
59
|
+
|
|
60
|
+
@Field
|
|
61
|
+
var initialDelayMs: Int = 1000
|
|
62
|
+
|
|
63
|
+
@Field
|
|
64
|
+
var delayMs: Int = 1000
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
struct ConnectConfig: Record {
|
|
68
|
+
@Field
|
|
69
|
+
var reconnectConfig: ReconnectConfig = ReconnectConfig()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
typealias RNTrackBandwidthLimit = Either<Int, [String: Int]>
|
|
73
|
+
|
|
74
|
+
public class RNFishjamClientModule: Module {
|
|
75
|
+
public func definition() -> ModuleDefinition {
|
|
76
|
+
Name("RNFishjamClient")
|
|
77
|
+
|
|
78
|
+
Events(
|
|
79
|
+
"IsCameraOn",
|
|
80
|
+
"IsMicrophoneOn",
|
|
81
|
+
"IsScreencastOn",
|
|
82
|
+
"SimulcastConfigUpdate",
|
|
83
|
+
"PeersUpdate",
|
|
84
|
+
"AudioDeviceUpdate",
|
|
85
|
+
"SendMediaEvent",
|
|
86
|
+
"BandwidthEstimation",
|
|
87
|
+
"ReconnectionRetriesLimitReached",
|
|
88
|
+
"ReconnectionStarted",
|
|
89
|
+
"Reconnected")
|
|
90
|
+
|
|
91
|
+
let rnFishjamClient: RNFishjamClient = {
|
|
92
|
+
let client = RNFishjamClient {
|
|
93
|
+
(eventName: String, data: [String: Any]) in
|
|
94
|
+
self.sendEvent(eventName, data)
|
|
95
|
+
}
|
|
96
|
+
client.create()
|
|
97
|
+
return client
|
|
98
|
+
}()
|
|
99
|
+
|
|
100
|
+
AsyncFunction("connect") {
|
|
101
|
+
(url: String, peerToken: String, peerMetadata: [String: Any], config: ConnectConfig, promise: Promise) in
|
|
102
|
+
rnFishjamClient.connect(
|
|
103
|
+
url: url, peerToken: peerToken, peerMetadata: peerMetadata, config: config, promise: promise)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
AsyncFunction("leaveRoom") {
|
|
107
|
+
rnFishjamClient.leaveRoom()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
AsyncFunction("startCamera") { (config: CameraConfig) in
|
|
111
|
+
try rnFishjamClient.startCamera(config: config)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Property("isMicrophoneOn") {
|
|
115
|
+
return rnFishjamClient.isMicrophoneOn
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AsyncFunction("toggleMicrophone") {
|
|
119
|
+
try rnFishjamClient.toggleMicrophone()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Property("isCameraOn") {
|
|
123
|
+
return rnFishjamClient.isCameraOn
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
AsyncFunction("toggleCamera") {
|
|
127
|
+
try rnFishjamClient.toggleCamera()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
AsyncFunction("flipCamera") {
|
|
131
|
+
try rnFishjamClient.flipCamera()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
AsyncFunction("switchCamera") { (captureDeviceId: String) in
|
|
135
|
+
try rnFishjamClient.switchCamera(captureDeviceId: captureDeviceId)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
AsyncFunction("getCaptureDevices") {
|
|
139
|
+
rnFishjamClient.getCaptureDevices()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
AsyncFunction("toggleScreencast") { (screencastOptions: ScreencastOptions) in
|
|
143
|
+
try rnFishjamClient.toggleScreencast(screencastOptions: screencastOptions)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Property("isScreencastOn") {
|
|
147
|
+
return rnFishjamClient.isScreencastOn
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
AsyncFunction("getPeers") {
|
|
151
|
+
try rnFishjamClient.getPeers()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
AsyncFunction("updatePeerMetadata") { (metadata: [String: Any]) in
|
|
155
|
+
try rnFishjamClient.updatePeerMetadata(metadata: metadata)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
AsyncFunction("updateVideoTrackMetadata") { (metadata: [String: Any]) in
|
|
159
|
+
try rnFishjamClient.updateLocalVideoTrackMetadata(metadata: metadata)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
AsyncFunction("updateAudioTrackMetadata") { (metadata: [String: Any]) in
|
|
163
|
+
try rnFishjamClient.updateLocalAudioTrackMetadata(metadata: metadata)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
AsyncFunction("updateScreencastTrackMetadata") { (metadata: [String: Any]) in
|
|
167
|
+
try rnFishjamClient.updateLocalScreencastTrackMetadata(metadata: metadata)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
AsyncFunction("toggleScreencastTrackEncoding") { (encoding: String) in
|
|
171
|
+
try rnFishjamClient.toggleScreencastTrackEncoding(encoding: encoding)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
AsyncFunction("setScreencastTrackBandwidth") { (bandwidth: Int) in
|
|
175
|
+
try rnFishjamClient.setScreencastTrackBandwidth(bandwidth: bandwidth)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
AsyncFunction("setScreencastTrackEncodingBandwidth") { (encoding: String, bandwidth: Int) in
|
|
179
|
+
try rnFishjamClient.setScreencastTrackEncodingBandwidth(
|
|
180
|
+
encoding: encoding, bandwidth: bandwidth)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
AsyncFunction("setTargetTrackEncoding") { (trackId: String, encoding: String) in
|
|
184
|
+
try rnFishjamClient.setTargetTrackEncoding(trackId: trackId, encoding: encoding)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
AsyncFunction("toggleVideoTrackEncoding") { (encoding: String) in
|
|
188
|
+
try rnFishjamClient.toggleVideoTrackEncoding(encoding: encoding)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
AsyncFunction("setVideoTrackEncodingBandwidth") { (encoding: String, bandwidth: Int) in
|
|
192
|
+
try rnFishjamClient.setVideoTrackEncodingBandwidth(
|
|
193
|
+
encoding: encoding, bandwidth: bandwidth)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
AsyncFunction("setVideoTrackBandwidth") { (bandwidth: Int) in
|
|
197
|
+
try rnFishjamClient.setVideoTrackBandwidth(bandwidth: bandwidth)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
AsyncFunction("changeWebRTCLoggingSeverity") { (severity: String) in
|
|
201
|
+
try rnFishjamClient.changeWebRTCLoggingSeverity(severity: severity)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
AsyncFunction("getStatistics") {
|
|
205
|
+
try rnFishjamClient.getStatistics()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
AsyncFunction("selectAudioSessionMode") { (sessionMode: String) in
|
|
209
|
+
try rnFishjamClient.selectAudioSessionMode(sessionMode: sessionMode)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
AsyncFunction("showAudioRoutePicker") {
|
|
213
|
+
rnFishjamClient.showAudioRoutePicker()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
AsyncFunction("startAudioSwitcher") {
|
|
217
|
+
rnFishjamClient.startAudioSwitcher()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
package/ios/Utils.swift
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import FishjamCloudClient
|
|
2
|
+
import ReplayKit
|
|
3
|
+
import os.log
|
|
4
|
+
|
|
5
|
+
let log = OSLog(subsystem: "com.fishjamcloud.react-native-client", category: "ErrorHandling")
|
|
6
|
+
|
|
7
|
+
#if os(iOS)
|
|
8
|
+
@available(iOS 12, *)
|
|
9
|
+
extension RPSystemBroadcastPickerView {
|
|
10
|
+
public static func show(
|
|
11
|
+
for preferredExtension: String? = nil, showsMicrophoneButton: Bool = false
|
|
12
|
+
) {
|
|
13
|
+
let view = RPSystemBroadcastPickerView()
|
|
14
|
+
view.preferredExtension = preferredExtension
|
|
15
|
+
view.showsMicrophoneButton = showsMicrophoneButton
|
|
16
|
+
|
|
17
|
+
let selector = NSSelectorFromString("buttonPressed:")
|
|
18
|
+
if view.responds(to: selector) {
|
|
19
|
+
view.perform(selector, with: nil)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
extension [String: Any] {
|
|
26
|
+
public func toMetadata() -> Metadata {
|
|
27
|
+
var res: Metadata = .init()
|
|
28
|
+
self.forEach { entry in
|
|
29
|
+
res[entry.key] = entry.value
|
|
30
|
+
}
|
|
31
|
+
return res
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
extension AnyJson {
|
|
36
|
+
public func toDict() -> [String: Any] {
|
|
37
|
+
var res: [String: Any] = [:]
|
|
38
|
+
self.keys.forEach { key in
|
|
39
|
+
res[key] = self[key]
|
|
40
|
+
}
|
|
41
|
+
return res
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import FishjamCloudClient
|
|
3
|
+
|
|
4
|
+
class VideoPreviewView: ExpoView {
|
|
5
|
+
var videoView: VideoView? = nil
|
|
6
|
+
private var localVideoTrack: LocalVideoTrack? = nil
|
|
7
|
+
|
|
8
|
+
required init(appContext: AppContext? = nil) {
|
|
9
|
+
super.init(appContext: appContext)
|
|
10
|
+
videoView = VideoView()
|
|
11
|
+
videoView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
12
|
+
videoView?.clipsToBounds = true
|
|
13
|
+
addSubview(videoView!)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override func willMove(toSuperview newSuperview: UIView?) {
|
|
17
|
+
super.willMove(toSuperview: newSuperview)
|
|
18
|
+
if newSuperview == nil {
|
|
19
|
+
localVideoTrack?.stop()
|
|
20
|
+
} else {
|
|
21
|
+
guard let tracks = RNFishjamClient.fishjamClient?.getLocalEndpoint().tracks else {
|
|
22
|
+
os_log(
|
|
23
|
+
"Error moving VideoPreviewView: %{public}s", log: log, type: .error,
|
|
24
|
+
String(describing: "No tracks available")
|
|
25
|
+
)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
localVideoTrack =
|
|
30
|
+
tracks.first(where: { (key, track) in
|
|
31
|
+
track is LocalVideoTrack
|
|
32
|
+
})?.value as? LocalVideoTrack
|
|
33
|
+
localVideoTrack?.start()
|
|
34
|
+
videoView?.track = localVideoTrack
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var videoLayout: String = "FILL" {
|
|
39
|
+
didSet {
|
|
40
|
+
switch videoLayout {
|
|
41
|
+
case "FIT":
|
|
42
|
+
self.videoView?.layout = .fit
|
|
43
|
+
case "FILL":
|
|
44
|
+
self.videoView?.layout = .fill
|
|
45
|
+
default:
|
|
46
|
+
self.videoView?.layout = .fill
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var mirrorVideo: Bool = false {
|
|
52
|
+
didSet {
|
|
53
|
+
self.videoView?.mirror = mirrorVideo
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var captureDeviceId: String? = nil {
|
|
58
|
+
didSet {
|
|
59
|
+
if let captureDeviceId = captureDeviceId {
|
|
60
|
+
localVideoTrack?.switchCamera(deviceId: captureDeviceId)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
public class VideoPreviewViewModule: Module {
|
|
4
|
+
public func definition() -> ModuleDefinition {
|
|
5
|
+
Name("VideoPreviewViewModule")
|
|
6
|
+
|
|
7
|
+
View(VideoPreviewView.self) {
|
|
8
|
+
Prop("captureDeviceId") { (view: VideoPreviewView, prop: String) in
|
|
9
|
+
view.captureDeviceId = prop
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
Prop("mirrorVideo") { (view: VideoPreviewView, prop: Bool) in
|
|
13
|
+
view.mirrorVideo = prop
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Prop("videoLayout") { (view: VideoPreviewView, prop: String) in
|
|
17
|
+
view.videoLayout = prop
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import ExpoModulesCore
|
|
3
|
+
import FishjamCloudClient
|
|
4
|
+
|
|
5
|
+
protocol OnTrackUpdateListener {
|
|
6
|
+
func onTrackUpdate()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class VideoRendererView: ExpoView, OnTrackUpdateListener {
|
|
10
|
+
var videoView: VideoView? = nil
|
|
11
|
+
var cancellableEndpoints: Cancellable? = nil
|
|
12
|
+
|
|
13
|
+
required init(appContext: AppContext? = nil) {
|
|
14
|
+
super.init(appContext: appContext)
|
|
15
|
+
RNFishjamClient.onTracksUpdateListeners.append(self)
|
|
16
|
+
videoView = VideoView()
|
|
17
|
+
videoView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
18
|
+
videoView?.clipsToBounds = true
|
|
19
|
+
addSubview(videoView!)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
deinit {
|
|
23
|
+
RNFishjamClient.onTracksUpdateListeners.removeAll(where: {
|
|
24
|
+
if let view = $0 as? VideoRendererView {
|
|
25
|
+
return view === self
|
|
26
|
+
}
|
|
27
|
+
return false
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override func willMove(toSuperview newSuperview: UIView?) {
|
|
32
|
+
super.willMove(toSuperview: newSuperview)
|
|
33
|
+
|
|
34
|
+
if newSuperview != nil {
|
|
35
|
+
updateVideoTrack()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func updateVideoTrack() {
|
|
40
|
+
DispatchQueue.main.async {
|
|
41
|
+
if self.superview != nil {
|
|
42
|
+
for endpoint in RNFishjamClient.getLocalAndRemoteEndpoints() {
|
|
43
|
+
if let track = endpoint.tracks[self.trackId] as? VideoTrack {
|
|
44
|
+
if let track = track as? LocalVideoTrack {
|
|
45
|
+
self.mirrorVideo = track.isFrontCamera
|
|
46
|
+
}
|
|
47
|
+
self.videoView?.track = track
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func onTrackUpdate() {
|
|
56
|
+
updateVideoTrack()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var trackId: String = "" {
|
|
60
|
+
didSet {
|
|
61
|
+
updateVideoTrack()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var videoLayout: String = "FILL" {
|
|
66
|
+
didSet {
|
|
67
|
+
switch videoLayout {
|
|
68
|
+
case "FIT":
|
|
69
|
+
self.videoView?.layout = .fit
|
|
70
|
+
case "FILL":
|
|
71
|
+
self.videoView?.layout = .fill
|
|
72
|
+
default:
|
|
73
|
+
self.videoView?.layout = .fill
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var mirrorVideo: Bool = false {
|
|
80
|
+
didSet {
|
|
81
|
+
self.videoView?.mirror = mirrorVideo
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
public class VideoRendererViewModule: Module {
|
|
4
|
+
public func definition() -> ModuleDefinition {
|
|
5
|
+
Name("VideoRendererViewModule")
|
|
6
|
+
|
|
7
|
+
View(VideoRendererView.self) {
|
|
8
|
+
Prop("trackId") { (view: VideoRendererView, prop: String) in
|
|
9
|
+
view.trackId = prop
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
Prop("videoLayout") { (view: VideoRendererView, prop: String) in
|
|
13
|
+
view.videoLayout = prop
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fileScopedDeclarationPrivacy": {
|
|
3
|
+
"accessLevel": "private"
|
|
4
|
+
},
|
|
5
|
+
"indentation": {
|
|
6
|
+
"spaces": 4
|
|
7
|
+
},
|
|
8
|
+
"indentConditionalCompilationBlocks": true,
|
|
9
|
+
"indentSwitchCaseLabels": false,
|
|
10
|
+
"lineBreakAroundMultilineExpressionChainComponents": false,
|
|
11
|
+
"lineBreakBeforeControlFlowKeywords": false,
|
|
12
|
+
"lineBreakBeforeEachArgument": false,
|
|
13
|
+
"lineBreakBeforeEachGenericRequirement": false,
|
|
14
|
+
"lineLength": 120,
|
|
15
|
+
"maximumBlankLines": 1,
|
|
16
|
+
"prioritizeKeepingFunctionOutputTogether": false,
|
|
17
|
+
"respectsExistingLineBreaks": true,
|
|
18
|
+
"rules": {
|
|
19
|
+
"AllPublicDeclarationsHaveDocumentation": false,
|
|
20
|
+
"AlwaysUseLowerCamelCase": false,
|
|
21
|
+
"AmbiguousTrailingClosureOverload": true,
|
|
22
|
+
"BeginDocumentationCommentWithOneLineSummary": false,
|
|
23
|
+
"DoNotUseSemicolons": true,
|
|
24
|
+
"DontRepeatTypeInStaticProperties": true,
|
|
25
|
+
"FileScopedDeclarationPrivacy": true,
|
|
26
|
+
"FullyIndirectEnum": true,
|
|
27
|
+
"GroupNumericLiterals": true,
|
|
28
|
+
"IdentifiersMustBeASCII": true,
|
|
29
|
+
"NeverForceUnwrap": false,
|
|
30
|
+
"NeverUseForceTry": false,
|
|
31
|
+
"NeverUseImplicitlyUnwrappedOptionals": false,
|
|
32
|
+
"NoAccessLevelOnExtensionDeclaration": true,
|
|
33
|
+
"NoBlockComments": false,
|
|
34
|
+
"NoCasesWithOnlyFallthrough": true,
|
|
35
|
+
"NoEmptyTrailingClosureParentheses": true,
|
|
36
|
+
"NoLabelsInCasePatterns": true,
|
|
37
|
+
"NoLeadingUnderscores": false,
|
|
38
|
+
"NoParensAroundConditions": true,
|
|
39
|
+
"NoVoidReturnOnFunctionSignature": true,
|
|
40
|
+
"OneCasePerLine": true,
|
|
41
|
+
"OneVariableDeclarationPerLine": true,
|
|
42
|
+
"OnlyOneTrailingClosureArgument": false,
|
|
43
|
+
"OrderedImports": true,
|
|
44
|
+
"ReturnVoidInsteadOfEmptyTuple": true,
|
|
45
|
+
"UseEarlyExits": false,
|
|
46
|
+
"UseLetInEveryBoundCaseVariable": true,
|
|
47
|
+
"UseShorthandTypeNames": true,
|
|
48
|
+
"UseSingleLinePropertyGetter": true,
|
|
49
|
+
"UseSynthesizedInitializer": true,
|
|
50
|
+
"UseTripleSlashForDocumentationComments": false,
|
|
51
|
+
"UseWhereClausesInForLoops": false,
|
|
52
|
+
"ValidateDocumentationComments": false
|
|
53
|
+
},
|
|
54
|
+
"tabWidth": 8,
|
|
55
|
+
"version": 1
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fishjam-cloud/react-native-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A React Native client for Fishjam Cloud",
|
|
5
|
+
"author": "Fishjam Cloud Team",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "build/index.js",
|
|
8
|
+
"types": "build/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "expo-module build",
|
|
11
|
+
"clean": "expo-module clean",
|
|
12
|
+
"lint": "sh ./scripts/lint.sh",
|
|
13
|
+
"lint:check": "sh ./scripts/lint_check.sh",
|
|
14
|
+
"test": "expo-module test",
|
|
15
|
+
"prepare": "expo-module prepare",
|
|
16
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
17
|
+
"expo-module": "expo-module",
|
|
18
|
+
"release": "release-it",
|
|
19
|
+
"releaseAppAndroid": "cd examples/video-chat/android && fastlane deploy",
|
|
20
|
+
"releaseAppIos": "cd example/ios && fastlane deploy",
|
|
21
|
+
"docs": "sed 's/\\.github\\/images/media/g' README.md > README_docs.md && typedoc src/index.tsx --media ./.github/images --readme README_docs.md && rm README_docs.md",
|
|
22
|
+
"typecheck": "yarn tsc --noEmit && cd example && yarn tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"react-native",
|
|
26
|
+
"expo",
|
|
27
|
+
"@fishjam-cloud/react-native-client",
|
|
28
|
+
"fishjam-cloud",
|
|
29
|
+
"MembraneWebRTC",
|
|
30
|
+
"fishjam"
|
|
31
|
+
],
|
|
32
|
+
"repository": "https://github.com/fishjam-cloud/mobile-client-sdk",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/fishjam-cloud/mobile-client-sdk/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/fishjam-cloud/mobile-client-sdk#readme",
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org/",
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"phoenix": "^1.7.6",
|
|
43
|
+
"promise-fs": "^2.1.1",
|
|
44
|
+
"protobufjs": "^7.4.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@testing-library/react": "^15.0.7",
|
|
48
|
+
"@types/lodash": "^4.17.7",
|
|
49
|
+
"@types/phoenix": "^1.6.5",
|
|
50
|
+
"@types/promise-fs": "^2.1.2",
|
|
51
|
+
"@types/react": "^18.3.5",
|
|
52
|
+
"expo": "^50.0.20",
|
|
53
|
+
"expo-module-scripts": "^3.0.4",
|
|
54
|
+
"expo-modules-core": "~1.11.8",
|
|
55
|
+
"husky": "^9.1.5",
|
|
56
|
+
"jest": "^29.6.1",
|
|
57
|
+
"jest-environment-jsdom": "^29.6.1",
|
|
58
|
+
"jest-expo": "^50.0.2",
|
|
59
|
+
"jest-websocket-mock": "^2.4.0",
|
|
60
|
+
"lint-staged": "^15.2.10",
|
|
61
|
+
"pod-install": "^0.2.0",
|
|
62
|
+
"react": "18.3.1",
|
|
63
|
+
"react-dom": "18.3.1",
|
|
64
|
+
"react-native": "0.73.4",
|
|
65
|
+
"release-it": "^17.6.0",
|
|
66
|
+
"ts-proto": "^2.1.0",
|
|
67
|
+
"typedoc": "^0.26.6",
|
|
68
|
+
"typedoc-plugin-mark-react-functional-components": "^0.2.2",
|
|
69
|
+
"typescript": "^5.5.4"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"expo": "*",
|
|
73
|
+
"react": "*",
|
|
74
|
+
"react-native": "*"
|
|
75
|
+
},
|
|
76
|
+
"jest": {
|
|
77
|
+
"preset": "jest-expo",
|
|
78
|
+
"modulePathIgnorePatterns": [
|
|
79
|
+
"<rootDir>/example/node_modules",
|
|
80
|
+
"<rootDir>/lib/"
|
|
81
|
+
],
|
|
82
|
+
"testEnvironment": "jsdom"
|
|
83
|
+
},
|
|
84
|
+
"lint-staged": {
|
|
85
|
+
"*.(js|ts|tsx)": [
|
|
86
|
+
"yarn lint:check"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDisplayName</key>
|
|
6
|
+
<string>MembraneScreenBroadcast</string>
|
|
7
|
+
<key>CFBundleExecutable</key>
|
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
9
|
+
<key>CFBundleIdentifier</key>
|
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
11
|
+
<key>CFBundleName</key>
|
|
12
|
+
<string>$(PRODUCT_NAME)</string>
|
|
13
|
+
<key>CFBundlePackageType</key>
|
|
14
|
+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
15
|
+
<key>CFBundleVersion</key>
|
|
16
|
+
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
17
|
+
<key>NSExtension</key>
|
|
18
|
+
<dict>
|
|
19
|
+
<key>NSExtensionPointIdentifier</key>
|
|
20
|
+
<string>com.apple.broadcast-services-upload</string>
|
|
21
|
+
<key>NSExtensionPrincipalClass</key>
|
|
22
|
+
<string>$(PRODUCT_MODULE_NAME).MembraneBroadcastSampleHandler</string>
|
|
23
|
+
<key>RPBroadcastProcessMode</key>
|
|
24
|
+
<string>RPBroadcastProcessModeSampleBuffer</string>
|
|
25
|
+
</dict>
|
|
26
|
+
</dict>
|
|
27
|
+
</plist>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import FishjamClient
|
|
2
|
+
import Foundation
|
|
3
|
+
import ReplayKit
|
|
4
|
+
import WebRTC
|
|
5
|
+
import os.log
|
|
6
|
+
|
|
7
|
+
/// App Group used by the extension to exchange buffers with the target application
|
|
8
|
+
let appGroup = "{{GROUP_IDENTIFIER}}"
|
|
9
|
+
|
|
10
|
+
let logger = OSLog(subsystem: "{{BUNDLE_IDENTIFIER}}.MembraneBroadcastSampleHandler", category: "Broadcaster")
|
|
11
|
+
|
|
12
|
+
/// An example `SampleHandler` utilizing `BroadcastSampleSource` from `MembraneRTC` sending broadcast samples and necessary notification enabling device's screencast.
|
|
13
|
+
class MembraneBroadcastSampleHandler: RPBroadcastSampleHandler {
|
|
14
|
+
let broadcastSource = BroadcastSampleSource(appGroup: appGroup)
|
|
15
|
+
var started: Bool = false
|
|
16
|
+
|
|
17
|
+
override func broadcastStarted(withSetupInfo _: [String: NSObject]?) {
|
|
18
|
+
started = broadcastSource.connect()
|
|
19
|
+
|
|
20
|
+
guard started else {
|
|
21
|
+
os_log("failed to connect with ipc server", log: logger, type: .debug)
|
|
22
|
+
|
|
23
|
+
super.finishBroadcastWithError(NSError(domain: "", code: 0, userInfo: nil))
|
|
24
|
+
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
broadcastSource.started()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override func broadcastPaused() {
|
|
32
|
+
broadcastSource.paused()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override func broadcastResumed() {
|
|
36
|
+
broadcastSource.resumed()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override func broadcastFinished() {
|
|
40
|
+
broadcastSource.finished()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
|
44
|
+
guard started else {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
broadcastSource.processFrame(sampleBuffer: sampleBuffer, ofType: sampleBufferType)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>com.apple.security.application-groups</key>
|
|
6
|
+
<array>
|
|
7
|
+
<string>{{GROUP_IDENTIFIER}}</string>
|
|
8
|
+
</array>
|
|
9
|
+
</dict>
|
|
10
|
+
</plist>
|