@fishjam-cloud/react-native-client 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -8
- package/android/build.gradle +5 -3
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/fishjam/reactnative/AudioDeviceKind.kt +34 -0
- package/android/src/main/java/io/fishjam/reactnative/AudioSwitchManager.kt +61 -0
- package/android/src/main/java/io/fishjam/reactnative/EmitableEvents.kt +14 -0
- package/android/src/main/java/io/fishjam/reactnative/Errors.kt +31 -0
- package/android/src/main/java/io/fishjam/reactnative/FishjamForegroundService.kt +90 -0
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClient.kt +870 -0
- package/android/src/main/java/io/fishjam/reactnative/RNFishjamClientModule.kt +370 -0
- package/android/src/main/java/io/fishjam/reactnative/Utils.kt +11 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewView.kt +40 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoPreviewViewModule.kt +17 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoRendererView.kt +58 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoRendererViewModule.kt +25 -0
- package/android/src/main/java/io/fishjam/reactnative/VideoView.kt +77 -0
- package/expo-module.config.json +17 -0
- package/ios/RNFishjamClient.podspec +30 -0
- package/ios/RNFishjamClientModule.swift +2 -2
- package/package.json +10 -7
- package/plugin/build/types.d.ts +9 -0
- package/plugin/build/types.js +2 -0
- package/plugin/build/withFishjam.d.ts +4 -0
- package/plugin/build/withFishjam.js +13 -0
- package/plugin/build/withFishjamAndroid.d.ts +3 -0
- package/plugin/build/withFishjamAndroid.js +32 -0
- package/plugin/build/withFishjamIos.d.ts +9 -0
- package/plugin/build/withFishjamIos.js +222 -0
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Add plugin to your `app.json` if it's not already added:
|
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
If you want to use screensharing feature, enable the following
|
|
51
|
+
If you want to use screensharing feature, enable the following flags:
|
|
52
52
|
|
|
53
53
|
```json
|
|
54
54
|
{
|
|
@@ -59,7 +59,12 @@ If you want to use screensharing feature, enable the following flag:
|
|
|
59
59
|
[
|
|
60
60
|
"@fishjam-cloud/react-native-client",
|
|
61
61
|
{
|
|
62
|
-
"
|
|
62
|
+
"android": {
|
|
63
|
+
"enableForegroundService": true
|
|
64
|
+
},
|
|
65
|
+
"ios": {
|
|
66
|
+
"enableScreensharing": true
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
69
|
]
|
|
65
70
|
]
|
|
@@ -72,11 +77,14 @@ On bare workflow run `expo prebuild` to configure the app, then run
|
|
|
72
77
|
|
|
73
78
|
### Android
|
|
74
79
|
|
|
75
|
-
1. Add camera and microphone permissions to your `AndroidManifest.xml
|
|
80
|
+
1. Add camera and microphone permissions to your `AndroidManifest.xml` or you app.json permissions.
|
|
76
81
|
2. Rebuild the app. That's it!
|
|
77
82
|
|
|
78
83
|
### iOS
|
|
79
84
|
|
|
85
|
+
> [!NOTE]
|
|
86
|
+
> If you're using our plugin with `enableScreensharing` set to `true`. Not further steps are required.
|
|
87
|
+
|
|
80
88
|
On iOS installation is a bit more complicated, because you need to setup a
|
|
81
89
|
screen broadcast app extension for screensharing.
|
|
82
90
|
|
|
@@ -102,7 +110,7 @@ screen broadcast app extension for screensharing.
|
|
|
102
110
|
Broadcast Upload Extension → Next. Choose the name for the new target, select
|
|
103
111
|
Swift language and deselect "Include UI Extension".
|
|
104
112
|
|
|
105
|
-

|
|
106
114
|
|
|
107
115
|
Press Finish. In the next alert xcode will ask you if you want to activate
|
|
108
116
|
the new scheme - press Cancel.
|
|
@@ -110,7 +118,7 @@ screen broadcast app extension for screensharing.
|
|
|
110
118
|
4. Configure app group. Go to "Signing & Capabilities" tab, click "+ Capability"
|
|
111
119
|
button in upper left corner and select "App Groups".
|
|
112
120
|
|
|
113
|
-

|
|
114
122
|
|
|
115
123
|
Then in the "App Groups" add a new group or select existing. Usually group
|
|
116
124
|
name has format `group.<your-bundle-identifier>`. Verify that both app and
|
|
@@ -119,7 +127,7 @@ screen broadcast app extension for screensharing.
|
|
|
119
127
|
5. A new folder with app extension should appear on the left with contents like
|
|
120
128
|
this:
|
|
121
129
|
|
|
122
|
-

|
|
123
131
|
|
|
124
132
|
Replace `SampleHandler.swift` with `MembraneBroadcastSampleHandler.swift` and
|
|
125
133
|
this code:
|
|
@@ -221,10 +229,12 @@ room client. To run the app:
|
|
|
221
229
|
instructions there to setup and run demo server.
|
|
222
230
|
2. Clone the repo
|
|
223
231
|
3. ```
|
|
224
|
-
cd `examples/fishjam-chat`
|
|
225
232
|
yarn
|
|
233
|
+
yarn build
|
|
234
|
+
cd `examples/fishjam-chat`
|
|
235
|
+
npx expo prebuild --clean
|
|
226
236
|
```
|
|
227
|
-
4.
|
|
237
|
+
4. Copy `.env.template` and create `.env` with proper secrets.
|
|
228
238
|
5. `yarn run android` or `yarn run ios` or run project from Android Studio /
|
|
229
239
|
Xcode just like every RN project. Note that simulators won't work, you have
|
|
230
240
|
to test on real device for camera and screensharing to run.
|
|
@@ -323,6 +333,9 @@ Use track metadata to differentiate between video and screencast tracks.
|
|
|
323
333
|
|
|
324
334
|
### Android foreground service
|
|
325
335
|
|
|
336
|
+
> [!NOTE]
|
|
337
|
+
> If you're using our plugin with `enableForegroundService` set to `true`. Not further steps are required.
|
|
338
|
+
|
|
326
339
|
In order for the call to continue running when app is in background, you need to
|
|
327
340
|
set up and start a foreground service. You can use a 3rd party library for this,
|
|
328
341
|
for example [notifee](https://notifee.app/).
|
package/android/build.gradle
CHANGED
|
@@ -2,8 +2,10 @@ apply plugin: 'com.android.library'
|
|
|
2
2
|
apply plugin: 'kotlin-android'
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
|
+
def packageVersion = '0.2.2'
|
|
6
|
+
|
|
5
7
|
group = 'io.fishjam.reactnative'
|
|
6
|
-
version =
|
|
8
|
+
version = "${packageVersion}"
|
|
7
9
|
|
|
8
10
|
buildscript {
|
|
9
11
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
@@ -59,7 +61,7 @@ android {
|
|
|
59
61
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
60
62
|
targetSdkVersion safeExtGet("targetSdkVersion", 31)
|
|
61
63
|
versionCode 1
|
|
62
|
-
versionName "
|
|
64
|
+
versionName "${packageVersion}"
|
|
63
65
|
}
|
|
64
66
|
lintOptions {
|
|
65
67
|
abortOnError false
|
|
@@ -82,7 +84,7 @@ dependencies {
|
|
|
82
84
|
implementation project(':fishjam-cloud-android-client')
|
|
83
85
|
} else {
|
|
84
86
|
// update version number when releasing
|
|
85
|
-
implementation
|
|
87
|
+
implementation "com.github.fishjam-cloud:mobile-client-sdk:${packageVersion}"
|
|
86
88
|
}
|
|
87
89
|
api 'com.github.davidliu:audioswitch:8edf84ee46cdbc84c2b7725aa8f8d66363e19876'
|
|
88
90
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import com.twilio.audioswitch.AudioDevice
|
|
4
|
+
|
|
5
|
+
enum class AudioDeviceKind(
|
|
6
|
+
val typeName: String,
|
|
7
|
+
val audioDeviceClass: Class<out AudioDevice>
|
|
8
|
+
) {
|
|
9
|
+
BLUETOOTH("bluetooth", AudioDevice.BluetoothHeadset::class.java),
|
|
10
|
+
WIRED_HEADSET("headset", AudioDevice.WiredHeadset::class.java),
|
|
11
|
+
SPEAKER("speaker", AudioDevice.Speakerphone::class.java),
|
|
12
|
+
EARPIECE("earpiece", AudioDevice.Earpiece::class.java)
|
|
13
|
+
;
|
|
14
|
+
|
|
15
|
+
companion object {
|
|
16
|
+
fun fromAudioDevice(audioDevice: AudioDevice): AudioDeviceKind? {
|
|
17
|
+
for (kind in values()) {
|
|
18
|
+
if (kind.audioDeviceClass == audioDevice.javaClass) {
|
|
19
|
+
return kind
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fun fromTypeName(typeName: String): AudioDeviceKind? {
|
|
26
|
+
for (kind in values()) {
|
|
27
|
+
if (kind.typeName == typeName) {
|
|
28
|
+
return kind
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import com.twilio.audioswitch.AudioDevice
|
|
7
|
+
import com.twilio.audioswitch.AudioDeviceChangeListener
|
|
8
|
+
import com.twilio.audioswitch.AudioSwitch
|
|
9
|
+
|
|
10
|
+
class AudioSwitchManager(
|
|
11
|
+
private val context: Context
|
|
12
|
+
) {
|
|
13
|
+
private var preferredDeviceList =
|
|
14
|
+
listOf(
|
|
15
|
+
AudioDevice.BluetoothHeadset::class.java,
|
|
16
|
+
AudioDevice.WiredHeadset::class.java,
|
|
17
|
+
AudioDevice.Speakerphone::class.java,
|
|
18
|
+
AudioDevice.Earpiece::class.java
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// AudioSwitch is not threadsafe, so all calls should be done on the main thread.
|
|
22
|
+
private val handler: Handler = Handler(Looper.getMainLooper())
|
|
23
|
+
|
|
24
|
+
private var audioSwitch: AudioSwitch =
|
|
25
|
+
AudioSwitch(context, loggingEnabled = true, preferredDeviceList = preferredDeviceList)
|
|
26
|
+
|
|
27
|
+
fun start(listener: AudioDeviceChangeListener) {
|
|
28
|
+
handler.removeCallbacksAndMessages(null)
|
|
29
|
+
handler.postAtFrontOfQueue {
|
|
30
|
+
audioSwitch.start(listener)
|
|
31
|
+
audioSwitch.activate()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fun stop() {
|
|
36
|
+
handler.removeCallbacksAndMessages(null)
|
|
37
|
+
handler.postAtFrontOfQueue {
|
|
38
|
+
audioSwitch.stop()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun selectedAudioDevice(): AudioDevice? = audioSwitch.selectedAudioDevice
|
|
43
|
+
|
|
44
|
+
fun availableAudioDevices(): List<AudioDevice> = audioSwitch.availableAudioDevices
|
|
45
|
+
|
|
46
|
+
fun selectAudioOutput(audioDeviceClass: Class<out AudioDevice?>) {
|
|
47
|
+
handler.post {
|
|
48
|
+
availableAudioDevices()
|
|
49
|
+
.find { it.javaClass == audioDeviceClass }
|
|
50
|
+
?.let {
|
|
51
|
+
audioSwitch.selectDevice(it)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun selectAudioOutput(kind: AudioDeviceKind?) {
|
|
57
|
+
if (kind != null) {
|
|
58
|
+
selectAudioOutput(kind.audioDeviceClass)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
object EmitableEvents {
|
|
4
|
+
const val IsCameraOn = "IsCameraOn"
|
|
5
|
+
const val IsMicrophoneOn = "IsMicrophoneOn"
|
|
6
|
+
const val IsScreencastOn = "IsScreencastOn"
|
|
7
|
+
const val SimulcastConfigUpdate = "SimulcastConfigUpdate"
|
|
8
|
+
const val PeersUpdate = "PeersUpdate"
|
|
9
|
+
const val AudioDeviceUpdate = "AudioDeviceUpdate"
|
|
10
|
+
const val BandwidthEstimation = "BandwidthEstimation"
|
|
11
|
+
const val ReconnectionRetriesLimitReached = "ReconnectionRetriesLimitReached"
|
|
12
|
+
const val ReconnectionStarted = "ReconnectionStarted"
|
|
13
|
+
const val Reconnected = "Reconnected"
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import com.fishjamcloud.client.models.AuthError
|
|
4
|
+
import expo.modules.kotlin.exception.CodedException
|
|
5
|
+
|
|
6
|
+
class JoinError(
|
|
7
|
+
metadata: Any
|
|
8
|
+
) : CodedException(message = "Join error: $metadata")
|
|
9
|
+
|
|
10
|
+
class ConnectionError(
|
|
11
|
+
reason: AuthError
|
|
12
|
+
) : CodedException(message = "Connection error: ${reason.error}")
|
|
13
|
+
|
|
14
|
+
class MissingScreencastPermission : CodedException(message = "No permission to start screencast, call handleScreencastPermission first.")
|
|
15
|
+
|
|
16
|
+
class ClientNotConnectedError : CodedException(message = "Client not connected to server yet. Make sure to call connect() first!")
|
|
17
|
+
|
|
18
|
+
class NoLocalVideoTrackError : CodedException(message = "No local video track. Make sure to call connect() first!")
|
|
19
|
+
|
|
20
|
+
class NoLocalAudioTrackError : CodedException(message = "No local audio track. Make sure to call connect() first!")
|
|
21
|
+
|
|
22
|
+
class NoScreencastTrackError : CodedException(message = "No local screencast track. Make sure to toggle screencast on first!")
|
|
23
|
+
|
|
24
|
+
class SocketClosedError(
|
|
25
|
+
code: Int,
|
|
26
|
+
reason: String
|
|
27
|
+
) : CodedException(message = "Socket was closed with code = $code and reason = $reason")
|
|
28
|
+
|
|
29
|
+
class SocketError(
|
|
30
|
+
message: String
|
|
31
|
+
) : CodedException(message = "Socket error: $message")
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
package io.fishjam.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.Notification
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.PendingIntent
|
|
7
|
+
import android.app.Service
|
|
8
|
+
import android.content.Intent
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.os.IBinder
|
|
11
|
+
import androidx.core.app.NotificationCompat
|
|
12
|
+
|
|
13
|
+
class FishjamForegroundService : Service() {
|
|
14
|
+
companion object {
|
|
15
|
+
private const val FOREGROUND_SERVICE_ID = 1668
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun onBind(p0: Intent?): IBinder? = null
|
|
19
|
+
|
|
20
|
+
override fun onStartCommand(
|
|
21
|
+
intent: Intent?,
|
|
22
|
+
flags: Int,
|
|
23
|
+
startId: Int
|
|
24
|
+
): Int {
|
|
25
|
+
val channelId = intent!!.getStringExtra("channelId")!!
|
|
26
|
+
val channelName = intent.getStringExtra("channelName")!!
|
|
27
|
+
val notificationTitle = intent.getStringExtra("notificationTitle")!!
|
|
28
|
+
val notificationContent = intent.getStringExtra("notificationContent")!!
|
|
29
|
+
val foregroundServiceTypesArray = intent.getIntArrayExtra("foregroundServiceTypes")!!
|
|
30
|
+
// Create "bitwise or" of foregroundServiceTypesArray
|
|
31
|
+
val foregroundServiceType = foregroundServiceTypesArray.reduce { acc, value -> acc or value }
|
|
32
|
+
|
|
33
|
+
val pendingIntent =
|
|
34
|
+
PendingIntent.getActivity(
|
|
35
|
+
this,
|
|
36
|
+
0,
|
|
37
|
+
intent,
|
|
38
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
val notification: Notification =
|
|
42
|
+
NotificationCompat
|
|
43
|
+
.Builder(this, channelId)
|
|
44
|
+
.setContentTitle(notificationTitle)
|
|
45
|
+
.setContentText(notificationContent)
|
|
46
|
+
.setContentIntent(pendingIntent)
|
|
47
|
+
.build()
|
|
48
|
+
|
|
49
|
+
createNotificationChannel(channelId, channelName)
|
|
50
|
+
startForegroundWithNotification(notification, foregroundServiceType)
|
|
51
|
+
|
|
52
|
+
return START_NOT_STICKY
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun startForegroundWithNotification(
|
|
56
|
+
notification: Notification,
|
|
57
|
+
foregroundServiceType: Int
|
|
58
|
+
) {
|
|
59
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
60
|
+
startForeground(
|
|
61
|
+
FOREGROUND_SERVICE_ID,
|
|
62
|
+
notification,
|
|
63
|
+
foregroundServiceType
|
|
64
|
+
)
|
|
65
|
+
} else {
|
|
66
|
+
startForeground(
|
|
67
|
+
FOREGROUND_SERVICE_ID,
|
|
68
|
+
notification
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun createNotificationChannel(
|
|
74
|
+
channelId: String,
|
|
75
|
+
channelName: String
|
|
76
|
+
) {
|
|
77
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
val serviceChannel =
|
|
82
|
+
NotificationChannel(
|
|
83
|
+
channelId,
|
|
84
|
+
channelName,
|
|
85
|
+
NotificationManager.IMPORTANCE_DEFAULT
|
|
86
|
+
)
|
|
87
|
+
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as? NotificationManager
|
|
88
|
+
notificationManager?.createNotificationChannel(serviceChannel)
|
|
89
|
+
}
|
|
90
|
+
}
|