@capgo/capacitor-stream-call 0.0.27 → 0.0.28

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.
@@ -24,7 +24,9 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
24
24
  CAPPluginMethod(name: "setCameraEnabled", returnType: CAPPluginReturnPromise),
25
25
  CAPPluginMethod(name: "acceptCall", returnType: CAPPluginReturnPromise),
26
26
  CAPPluginMethod(name: "isCameraEnabled", returnType: CAPPluginReturnPromise),
27
- CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise)
27
+ CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "setSpeaker", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "switchCamera", returnType: CAPPluginReturnPromise)
28
30
  ]
29
31
 
30
32
  private enum State {
@@ -792,4 +794,66 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
792
794
  "state": currentCallState
793
795
  ])
794
796
  }
797
+
798
+ @objc func setSpeaker(_ call: CAPPluginCall) {
799
+ guard let name = call.getString("name") else {
800
+ call.reject("Missing required parameter: name")
801
+ return
802
+ }
803
+
804
+ do {
805
+ try requireInitialized()
806
+
807
+ Task {
808
+ do {
809
+ if let activeCall = streamVideo?.state.activeCall {
810
+ if name == "speaker" {
811
+ try await activeCall.speaker.enableSpeakerPhone()
812
+ } else {
813
+ try await activeCall.speaker.disableSpeakerPhone()
814
+ }
815
+ }
816
+ call.resolve([
817
+ "success": true
818
+ ])
819
+ } catch {
820
+ log.error("Error setting speaker: \(String(describing: error))")
821
+ call.reject("Failed to set speaker: \(error.localizedDescription)")
822
+ }
823
+ }
824
+ } catch {
825
+ call.reject("StreamVideo not initialized")
826
+ }
827
+ }
828
+
829
+ @objc func switchCamera(_ call: CAPPluginCall) {
830
+ guard let camera = call.getString("camera") else {
831
+ call.reject("Missing required parameter: camera")
832
+ return
833
+ }
834
+
835
+ do {
836
+ try requireInitialized()
837
+
838
+ Task {
839
+ do {
840
+ if let activeCall = streamVideo?.state.activeCall {
841
+ if (camera == "front" && activeCall.camera.direction != .front) ||
842
+ (camera == "back" && activeCall.camera.direction != .back) {
843
+ try await activeCall.camera.flip()
844
+ }
845
+ }
846
+ call.resolve([
847
+ "success": true
848
+ ])
849
+ } catch {
850
+ log.error("Error switching camera: \(String(describing: error))")
851
+ call.reject("Failed to switch camera: \(error.localizedDescription)")
852
+ }
853
+ }
854
+ } catch {
855
+ call.reject("StreamVideo not initialized")
856
+ }
857
+ }
858
+
795
859
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "description": "Uses the https://getstream.io/ SDK to implement calling in Capacitor",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -1,217 +0,0 @@
1
- package ee.forgr.capacitor.streamcall
2
-
3
- import android.content.Context
4
- import android.widget.Toast
5
- import androidx.compose.foundation.background
6
- import androidx.compose.foundation.layout.Box
7
- import androidx.compose.foundation.layout.BoxScope
8
- import androidx.compose.foundation.layout.WindowInsets
9
- import androidx.compose.foundation.layout.asPaddingValues
10
- import androidx.compose.foundation.layout.fillMaxSize
11
- import androidx.compose.foundation.layout.padding
12
- import androidx.compose.foundation.layout.safeDrawing
13
- import androidx.compose.material3.Text
14
- import androidx.compose.runtime.Composable
15
- import androidx.compose.runtime.LaunchedEffect
16
- import androidx.compose.runtime.collectAsState
17
- import androidx.compose.runtime.getValue
18
- import androidx.compose.runtime.mutableStateOf
19
- import androidx.compose.runtime.remember
20
- import androidx.compose.runtime.setValue
21
- import androidx.compose.ui.Alignment
22
- import androidx.compose.ui.Modifier
23
- import androidx.compose.ui.draw.clip
24
- import androidx.compose.ui.layout.onSizeChanged
25
- import androidx.compose.ui.platform.LocalConfiguration
26
- import androidx.compose.ui.platform.LocalInspectionMode
27
- import androidx.compose.ui.platform.LocalLayoutDirection
28
- import androidx.compose.ui.platform.LocalDensity
29
- import androidx.compose.ui.text.style.TextAlign
30
- import androidx.compose.ui.unit.IntSize
31
- import androidx.compose.ui.unit.dp
32
- import androidx.compose.ui.unit.sp
33
- import io.getstream.video.android.compose.permission.LaunchCallPermissions
34
- import io.getstream.video.android.compose.theme.VideoTheme
35
- import io.getstream.video.android.compose.ui.components.call.renderer.FloatingParticipantVideo
36
- import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantVideo
37
- import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantsLayout
38
- import io.getstream.video.android.compose.ui.components.call.renderer.RegularVideoRendererStyle
39
- import io.getstream.video.android.compose.ui.components.call.renderer.VideoRendererStyle
40
- import io.getstream.video.android.core.ParticipantState
41
- import io.getstream.video.android.core.RealtimeConnection
42
- import io.getstream.video.android.core.StreamVideo
43
- import io.getstream.video.android.core.Call
44
- import io.getstream.video.android.compose.ui.components.video.VideoScalingType
45
- import androidx.compose.ui.graphics.Color
46
-
47
- @Composable
48
- fun CallOverlayView(
49
- context: Context,
50
- streamVideo: StreamVideo?,
51
- call: Call?
52
- ) {
53
- if (streamVideo == null) {
54
- Box(
55
- modifier = Modifier
56
- .fillMaxSize()
57
- .background(Color.Red)
58
- )
59
- return
60
- }
61
-
62
- // Collect the active call state
63
- //val activeCall by internalInstance.state.activeCall.collectAsState()
64
- // val call = activeCall
65
- if (call == null) {
66
- Box(
67
- modifier = Modifier
68
- .fillMaxSize()
69
- .background(Color.LightGray)
70
- )
71
- return
72
- }
73
-
74
- // Handle permissions in the Composable context
75
- LaunchCallPermissions(
76
- call = call,
77
- onAllPermissionsGranted = {
78
- try {
79
- // Check session using reflection before joining
80
- val callClass = call.javaClass
81
- val sessionField = callClass.getDeclaredField("session")
82
- sessionField.isAccessible = true
83
- val sessionValue = sessionField.get(call)
84
-
85
- if (sessionValue != null) {
86
- android.util.Log.d("CallOverlayView", "Session already exists, skipping join")
87
- } else {
88
- android.util.Log.d("CallOverlayView", "No existing session, attempting to join call")
89
- val result = call.join(create = true)
90
- result.onError {
91
- android.util.Log.d("CallOverlayView", "Error joining call")
92
- Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
93
- }
94
- }
95
- } catch (e: Exception) {
96
- android.util.Log.e("CallOverlayView", "Error checking session or joining call", e)
97
- Toast.makeText(context, "Failed to join call: ${e.message}", Toast.LENGTH_LONG).show()
98
- }
99
- }
100
- )
101
-
102
- // Apply VideoTheme
103
- VideoTheme {
104
- // Define required properties.
105
- val allParticipants by call.state.participants.collectAsState()
106
- val remoteParticipants = allParticipants.filter { !it.isLocal }
107
- val remoteParticipantsCount by call.state.participantCounts.collectAsState()
108
- val connection by call.state.connection.collectAsState()
109
- val sessionId by call.state.session.collectAsState()
110
- var parentSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
111
-
112
- // Add logging for debugging
113
- LaunchedEffect(allParticipants, remoteParticipants, remoteParticipantsCount, connection, sessionId) {
114
- android.util.Log.d("CallOverlayView", "Detailed State Update:")
115
- android.util.Log.d("CallOverlayView", "- Call ID: ${call.id}")
116
- android.util.Log.d("CallOverlayView", "- Session ID: ${sessionId?.id}")
117
- android.util.Log.d("CallOverlayView", "- All Participants: $allParticipants")
118
- android.util.Log.d("CallOverlayView", "- Remote Participants: $remoteParticipants")
119
- android.util.Log.d("CallOverlayView", "- Remote Participant Count: $remoteParticipantsCount")
120
- android.util.Log.d("CallOverlayView", "- Connection State: $connection")
121
-
122
- // Log each participant's details
123
- allParticipants.forEach { participant ->
124
- android.util.Log.d("CallOverlayView", "Participant Details:")
125
- android.util.Log.d("CallOverlayView", "- ID: ${participant.userId}")
126
- android.util.Log.d("CallOverlayView", "- Is Local: ${participant.isLocal}")
127
- android.util.Log.d("CallOverlayView", "- Has Video: ${participant.videoEnabled}")
128
- android.util.Log.d("CallOverlayView", "- Has Audio: ${participant.audioEnabled}")
129
- }
130
- }
131
-
132
- Box(
133
- contentAlignment = Alignment.Center,
134
- modifier = Modifier
135
- .fillMaxSize()
136
- .background(VideoTheme.colors.baseSenary)
137
- .padding(WindowInsets.safeDrawing.asPaddingValues())
138
- .onSizeChanged { parentSize = it }
139
- ) {
140
- val videoRenderer: @Composable (
141
- modifier: Modifier,
142
- call: Call,
143
- participant: ParticipantState,
144
- style: VideoRendererStyle,
145
- ) -> Unit = { videoModifier, videoCall, videoParticipant, videoStyle ->
146
- ParticipantVideo(
147
- modifier = videoModifier,
148
- call = videoCall,
149
- participant = videoParticipant,
150
- style = videoStyle,
151
- scalingType = VideoScalingType.SCALE_ASPECT_FIT,
152
- actionsContent = { _, _, _ -> }
153
- )
154
- }
155
- val videoRendererNoAction: @Composable (ParticipantState) -> Unit =
156
- { participant ->
157
- ParticipantVideo(
158
- modifier = Modifier
159
- .fillMaxSize()
160
- .clip(VideoTheme.shapes.dialog),
161
- call = call,
162
- participant = participant,
163
- style = RegularVideoRendererStyle(),
164
- scalingType = VideoScalingType.SCALE_ASPECT_FIT,
165
- actionsContent = { _, _, _ -> }
166
- )
167
- }
168
- val floatingVideoRender: @Composable BoxScope.(
169
- call: Call,
170
- parentSize: IntSize
171
- ) -> Unit = { call, _ ->
172
- val participants by call.state.participants.collectAsState()
173
- val me = participants.firstOrNull { it.isLocal }
174
- me?.let { localParticipant ->
175
- val configuration = LocalConfiguration.current
176
- val layoutDirection = LocalLayoutDirection.current
177
- val density = LocalDensity.current
178
- val safeDrawingPadding = WindowInsets.safeDrawing.asPaddingValues()
179
- val adjustedSize = with(density) {
180
- IntSize(
181
- width = (configuration.screenWidthDp.dp.toPx() - safeDrawingPadding.calculateLeftPadding(layoutDirection).toPx() - safeDrawingPadding.calculateRightPadding(layoutDirection).toPx()).toInt(),
182
- height = (configuration.screenHeightDp.dp.toPx() - safeDrawingPadding.calculateTopPadding().toPx() - safeDrawingPadding.calculateBottomPadding().toPx()).toInt()
183
- )
184
- }
185
- FloatingParticipantVideo(
186
- call = call,
187
- videoRenderer = videoRendererNoAction,
188
- participant = if (LocalInspectionMode.current) {
189
- participants.first()
190
- } else {
191
- localParticipant
192
- },
193
- style = RegularVideoRendererStyle(),
194
- parentBounds = adjustedSize,
195
- )
196
- }
197
- }
198
-
199
- if (remoteParticipants.isNotEmpty()) {
200
- android.util.Log.d("CallOverlayView", "Showing ParticipantsLayout with ${remoteParticipants.size} remote participants")
201
- ParticipantsLayout(
202
- modifier = Modifier
203
- .fillMaxSize(),
204
- call = call,
205
- videoRenderer = videoRenderer,
206
- floatingVideoRenderer = floatingVideoRender
207
- )
208
- } else {
209
- Box(
210
- modifier = Modifier
211
- .fillMaxSize()
212
- .background(VideoTheme.colors.baseSenary)
213
- )
214
- }
215
- }
216
- }
217
- }
@@ -1,163 +0,0 @@
1
- package ee.forgr.capacitor.streamcall
2
-
3
- import android.util.Log
4
- import androidx.compose.foundation.background
5
- import androidx.compose.foundation.layout.Arrangement
6
- import androidx.compose.foundation.layout.Box
7
- import androidx.compose.foundation.layout.Column
8
- import androidx.compose.foundation.layout.WindowInsets
9
- import androidx.compose.foundation.layout.asPaddingValues
10
- import androidx.compose.foundation.layout.calculateEndPadding
11
- import androidx.compose.foundation.layout.calculateStartPadding
12
- import androidx.compose.foundation.layout.fillMaxSize
13
- import androidx.compose.foundation.layout.navigationBars
14
- import androidx.compose.foundation.layout.padding
15
- import androidx.compose.foundation.layout.safeDrawing
16
- import androidx.compose.foundation.layout.statusBars
17
- import androidx.compose.runtime.Composable
18
- import androidx.compose.runtime.LaunchedEffect
19
- import androidx.compose.runtime.collectAsState
20
- import androidx.compose.runtime.getValue
21
- import androidx.compose.ui.Alignment
22
- import androidx.compose.ui.Modifier
23
- import androidx.compose.ui.graphics.Color
24
- import androidx.compose.ui.platform.LocalLayoutDirection
25
- import io.getstream.video.android.compose.theme.VideoTheme
26
- import io.getstream.video.android.compose.ui.components.background.CallBackground
27
- import io.getstream.video.android.compose.ui.components.call.ringing.incomingcall.IncomingCallControls
28
- import io.getstream.video.android.compose.ui.components.call.ringing.incomingcall.IncomingCallDetails
29
- import io.getstream.video.android.core.Call
30
- import io.getstream.video.android.core.MemberState
31
- import io.getstream.video.android.core.RingingState
32
- import io.getstream.video.android.core.StreamVideo
33
- import io.getstream.video.android.core.call.state.AcceptCall
34
- import io.getstream.video.android.core.call.state.DeclineCall
35
- import io.getstream.video.android.core.call.state.ToggleCamera
36
- import io.getstream.video.android.model.User
37
-
38
- @Composable
39
- fun IncomingCallView(
40
- streamVideo: StreamVideo?,
41
- call: Call? = null,
42
- onDeclineCall: ((Call) -> Unit)? = null,
43
- onAcceptCall: ((Call) -> Unit)? = null,
44
- onHideIncomingCall: (() -> Unit)? = null
45
- ) {
46
- val ringingState = call?.state?.ringingState?.collectAsState(initial = RingingState.Idle)
47
-
48
- LaunchedEffect(ringingState?.value) {
49
- Log.d("IncomingCallView", "Changing ringingState to ${ringingState?.value}")
50
- when (ringingState?.value) {
51
- RingingState.TimeoutNoAnswer, RingingState.RejectedByAll -> {
52
- Log.d("IncomingCallView", "Call ended (${ringingState.value}), hiding incoming call view")
53
- onHideIncomingCall?.invoke()
54
- }
55
- RingingState.Active -> {
56
- Log.d("IncomingCallView", "Call accepted, hiding incoming call view")
57
- onHideIncomingCall?.invoke()
58
- }
59
- else -> {
60
- // Keep the view visible for other states
61
- }
62
- }
63
- }
64
-
65
- if (ringingState != null) {
66
- Log.d("IncomingCallView", "Ringing state changed to: ${ringingState.value}")
67
- }
68
-
69
- val backgroundColor = when {
70
- streamVideo == null -> Color.Cyan
71
- call == null -> Color.Red
72
- else -> Color.Green
73
- }
74
-
75
- if (call !== null) {
76
- // val participants by call.state.participants.collectAsState()
77
- // val members by call.state.members.collectAsState()
78
- // call.state.session
79
- val session by call.state.session.collectAsState()
80
- val isCameraEnabled by call.camera.isEnabled.collectAsState()
81
- val isVideoType = true
82
-
83
- val statusBarPadding = WindowInsets.statusBars.asPaddingValues()
84
- val navigationBarPadding = WindowInsets.navigationBars.asPaddingValues()
85
- val safeDrawingPadding = WindowInsets.safeDrawing.asPaddingValues()
86
- val layoutDirection = LocalLayoutDirection.current
87
-
88
- // println("participants: ${participants.map { it.name.value }} Members: ${members}")
89
-
90
- VideoTheme {
91
- CallBackground(
92
- modifier = Modifier.fillMaxSize(),
93
- ) {
94
- Column(
95
- modifier = Modifier
96
- .fillMaxSize()
97
- .padding(
98
- top = statusBarPadding.calculateTopPadding(),
99
- bottom = navigationBarPadding.calculateBottomPadding()
100
- ),
101
- horizontalAlignment = Alignment.CenterHorizontally,
102
- verticalArrangement = Arrangement.SpaceBetween
103
- ) {
104
- IncomingCallDetails(
105
- modifier = Modifier
106
- .padding(
107
- top = VideoTheme.dimens.spacingXl,
108
- start = safeDrawingPadding.calculateStartPadding(layoutDirection),
109
- end = safeDrawingPadding.calculateEndPadding(layoutDirection)
110
- ),
111
- isVideoType = isVideoType,
112
- participants = (session?.participants?.map { MemberState(
113
- user = User(
114
- id = it.user.id,
115
- name = it.user.id,
116
- image = it.user.image
117
- ),
118
- custom = mapOf(),
119
- role = it.role,
120
- createdAt = org.threeten.bp.OffsetDateTime.now(),
121
- updatedAt = org.threeten.bp.OffsetDateTime.now(),
122
- deletedAt = org.threeten.bp.OffsetDateTime.now(),
123
- acceptedAt = org.threeten.bp.OffsetDateTime.now(),
124
- rejectedAt = org.threeten.bp.OffsetDateTime.now()
125
- ) }?.filter { it.user.id != streamVideo?.userId }) ?: listOf()
126
- )
127
-
128
- IncomingCallControls(
129
- modifier = Modifier
130
- .padding(
131
- bottom = VideoTheme.dimens.spacingL,
132
- start = safeDrawingPadding.calculateStartPadding(layoutDirection),
133
- end = safeDrawingPadding.calculateEndPadding(layoutDirection)
134
- ),
135
- isVideoCall = isVideoType,
136
- isCameraEnabled = isCameraEnabled,
137
- onCallAction = { action ->
138
- when (action) {
139
- DeclineCall -> {
140
- onDeclineCall?.invoke(call)
141
- }
142
- AcceptCall -> {
143
- call.camera.setEnabled(isCameraEnabled)
144
- onAcceptCall?.invoke(call)
145
- }
146
- is ToggleCamera -> {
147
- call.camera.setEnabled(action.isEnabled)
148
- }
149
- else -> { /* ignore other actions */ }
150
- }
151
- }
152
- )
153
- }
154
- }
155
- }
156
- } else {
157
- Box(
158
- modifier = Modifier
159
- .fillMaxSize()
160
- .background(backgroundColor)
161
- )
162
- }
163
- }