@capgo/capacitor-stream-call 0.0.43 → 0.0.50
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 +110 -106
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +176 -31
- package/dist/docs.json +124 -0
- package/dist/esm/definitions.d.ts +34 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -0
- package/dist/esm/web.js +87 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +87 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +87 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +268 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,123 +140,96 @@ Add string resources for different languages:
|
|
|
140
140
|
</resources>
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
##
|
|
143
|
+
## Displaying Caller Information
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
On Android, to handle incoming calls when the device is locked, you need to listen for the `incomingCall` event and manage the call state properly:
|
|
145
|
+
When receiving incoming calls, you can access caller information including name, user ID, and profile image. This information is automatically extracted from the call data and passed through the event system.
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
import { StreamCall } from '@capgo/capacitor-stream-call';
|
|
147
|
+
### Getting Caller Information
|
|
150
148
|
|
|
151
|
-
|
|
152
|
-
private incomingCallId: string | null = null;
|
|
153
|
-
private isLockscreenIncoming = false;
|
|
149
|
+
The caller information is available in two ways:
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
this.setupIncomingCallListener();
|
|
157
|
-
}
|
|
151
|
+
**1. Through Call Events**
|
|
158
152
|
|
|
159
|
-
|
|
160
|
-
StreamCall.addListener('incomingCall', async (payload) => {
|
|
161
|
-
console.log('Incoming call from lockscreen:', payload);
|
|
162
|
-
|
|
163
|
-
// Store the call ID and show incoming call UI
|
|
164
|
-
this.incomingCallId = payload.cid;
|
|
165
|
-
this.isLockscreenIncoming = true;
|
|
166
|
-
|
|
167
|
-
// Show your custom incoming call screen
|
|
168
|
-
this.showIncomingCallScreen(payload);
|
|
169
|
-
});
|
|
170
|
-
}
|
|
153
|
+
The `callEvent` listener provides caller information for incoming calls:
|
|
171
154
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
await StreamCall.acceptCall();
|
|
181
|
-
this.clearIncomingCall();
|
|
182
|
-
console.log('Call accepted successfully');
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error('Failed to accept call:', error);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Reject the incoming call
|
|
189
|
-
async rejectCall() {
|
|
190
|
-
if (!this.incomingCallId) {
|
|
191
|
-
console.warn('No incoming call to reject');
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
await StreamCall.rejectCall();
|
|
197
|
-
this.clearIncomingCall();
|
|
198
|
-
console.log('Call rejected successfully');
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error('Failed to reject call:', error);
|
|
201
|
-
}
|
|
155
|
+
```typescript
|
|
156
|
+
StreamCall.addListener('callEvent', (event) => {
|
|
157
|
+
if (event.state === 'ringing' && event.caller) {
|
|
158
|
+
console.log('Incoming call from:', event.caller.name || event.caller.userId);
|
|
159
|
+
console.log('Caller image:', event.caller.imageURL);
|
|
160
|
+
// Update your UI to show caller information
|
|
161
|
+
showIncomingCallUI(event.caller);
|
|
202
162
|
}
|
|
163
|
+
});
|
|
164
|
+
```
|
|
203
165
|
|
|
204
|
-
|
|
205
|
-
this.incomingCallId = null;
|
|
206
|
-
this.isLockscreenIncoming = false;
|
|
207
|
-
// Hide your incoming call UI
|
|
208
|
-
this.hideIncomingCallScreen();
|
|
209
|
-
}
|
|
166
|
+
**2. Through Incoming Call Events (Android lock-screen)**
|
|
210
167
|
|
|
211
|
-
|
|
212
|
-
// Implement your custom incoming call UI
|
|
213
|
-
// This should show accept/reject buttons and caller information
|
|
214
|
-
// Example: navigate to incoming call page or show modal
|
|
215
|
-
}
|
|
168
|
+
The `incomingCall` listener also includes caller information:
|
|
216
169
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
170
|
+
```typescript
|
|
171
|
+
StreamCall.addListener('incomingCall', (payload) => {
|
|
172
|
+
if (payload.caller) {
|
|
173
|
+
console.log('Lock-screen call from:', payload.caller.name || payload.caller.userId);
|
|
174
|
+
// Update your lock-screen UI
|
|
175
|
+
updateLockScreenUI(payload.caller);
|
|
220
176
|
}
|
|
221
|
-
}
|
|
177
|
+
});
|
|
222
178
|
```
|
|
223
179
|
|
|
224
|
-
|
|
180
|
+
### Caller Information Structure
|
|
225
181
|
|
|
226
|
-
### Basic Call Operations
|
|
227
182
|
```typescript
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
apiKey: 'your_api_key',
|
|
236
|
-
magicDivId: 'video-container'
|
|
237
|
-
});
|
|
183
|
+
interface CallMember {
|
|
184
|
+
userId: string; // User ID (always present)
|
|
185
|
+
name?: string; // Display name (optional)
|
|
186
|
+
imageURL?: string; // Profile image URL (optional)
|
|
187
|
+
role?: string; // User role (optional)
|
|
188
|
+
}
|
|
189
|
+
```
|
|
238
190
|
|
|
239
|
-
|
|
240
|
-
await StreamCall.call({
|
|
241
|
-
userIds: ['user_to_call'],
|
|
242
|
-
type: 'default',
|
|
243
|
-
ring: true
|
|
244
|
-
});
|
|
191
|
+
### Example Implementation
|
|
245
192
|
|
|
246
|
-
|
|
247
|
-
await StreamCall.endCall();
|
|
193
|
+
Here's how to implement a proper incoming call screen with caller information:
|
|
248
194
|
|
|
249
|
-
|
|
250
|
-
|
|
195
|
+
```typescript
|
|
196
|
+
export class CallService {
|
|
197
|
+
private callerInfo: CallMember | null = null;
|
|
251
198
|
|
|
252
|
-
|
|
253
|
-
|
|
199
|
+
constructor() {
|
|
200
|
+
this.setupCallListeners();
|
|
201
|
+
}
|
|
254
202
|
|
|
255
|
-
|
|
256
|
-
|
|
203
|
+
private setupCallListeners() {
|
|
204
|
+
StreamCall.addListener('callEvent', (event) => {
|
|
205
|
+
if (event.state === 'ringing') {
|
|
206
|
+
this.callerInfo = event.caller || null;
|
|
207
|
+
this.showIncomingCallScreen();
|
|
208
|
+
} else if (event.state === 'joined' || event.state === 'left') {
|
|
209
|
+
this.callerInfo = null;
|
|
210
|
+
this.hideIncomingCallScreen();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
257
213
|
|
|
258
|
-
//
|
|
259
|
-
|
|
214
|
+
// Android lock-screen support
|
|
215
|
+
if (Capacitor.getPlatform() === 'android') {
|
|
216
|
+
StreamCall.addListener('incomingCall', (payload) => {
|
|
217
|
+
this.callerInfo = payload.caller || null;
|
|
218
|
+
this.showLockScreenIncomingCall();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private showIncomingCallScreen() {
|
|
224
|
+
const callerName = this.callerInfo?.name || 'Unknown Caller';
|
|
225
|
+
const callerImage = this.callerInfo?.imageURL || 'default-avatar.png';
|
|
226
|
+
|
|
227
|
+
// Update your UI components
|
|
228
|
+
document.getElementById('caller-name').textContent = callerName;
|
|
229
|
+
document.getElementById('caller-image').src = callerImage;
|
|
230
|
+
document.getElementById('incoming-call-screen').style.display = 'block';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
260
233
|
```
|
|
261
234
|
|
|
262
235
|
## API
|
|
@@ -278,6 +251,7 @@ await StreamCall.logout();
|
|
|
278
251
|
* [`getCallStatus()`](#getcallstatus)
|
|
279
252
|
* [`setSpeaker(...)`](#setspeaker)
|
|
280
253
|
* [`switchCamera(...)`](#switchcamera)
|
|
254
|
+
* [`getCallInfo(...)`](#getcallinfo)
|
|
281
255
|
* [Interfaces](#interfaces)
|
|
282
256
|
* [Type Aliases](#type-aliases)
|
|
283
257
|
* [Enums](#enums)
|
|
@@ -515,6 +489,23 @@ Switch camera
|
|
|
515
489
|
--------------------
|
|
516
490
|
|
|
517
491
|
|
|
492
|
+
### getCallInfo(...)
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
getCallInfo(options: { callId: string; }) => Promise<CallEvent>
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Get detailed information about an active call including caller details
|
|
499
|
+
|
|
500
|
+
| Param | Type | Description |
|
|
501
|
+
| ------------- | -------------------------------- | -------------------------------- |
|
|
502
|
+
| **`options`** | <code>{ callId: string; }</code> | - Options containing the call ID |
|
|
503
|
+
|
|
504
|
+
**Returns:** <code>Promise<<a href="#callevent">CallEvent</a>></code>
|
|
505
|
+
|
|
506
|
+
--------------------
|
|
507
|
+
|
|
508
|
+
|
|
518
509
|
### Interfaces
|
|
519
510
|
|
|
520
511
|
|
|
@@ -549,12 +540,14 @@ Switch camera
|
|
|
549
540
|
|
|
550
541
|
#### CallEvent
|
|
551
542
|
|
|
552
|
-
| Prop
|
|
553
|
-
|
|
|
554
|
-
| **`callId`**
|
|
555
|
-
| **`state`**
|
|
556
|
-
| **`userId`**
|
|
557
|
-
| **`reason`**
|
|
543
|
+
| Prop | Type | Description |
|
|
544
|
+
| ------------- | ------------------------------------------------- | -------------------------------------------------------------- |
|
|
545
|
+
| **`callId`** | <code>string</code> | ID of the call |
|
|
546
|
+
| **`state`** | <code><a href="#callstate">CallState</a></code> | Current state of the call |
|
|
547
|
+
| **`userId`** | <code>string</code> | User ID of the participant in the call who triggered the event |
|
|
548
|
+
| **`reason`** | <code>string</code> | Reason for the call state change, if applicable |
|
|
549
|
+
| **`caller`** | <code><a href="#callmember">CallMember</a></code> | Information about the caller (for incoming calls) |
|
|
550
|
+
| **`members`** | <code>CallMember[]</code> | List of call members |
|
|
558
551
|
|
|
559
552
|
|
|
560
553
|
#### CallState
|
|
@@ -748,12 +741,23 @@ The JSON representation for <a href="#listvalue">`ListValue`</a> is JSON array.
|
|
|
748
741
|
| **`sessionId`** | <code>string</code> | the user sesion_id to pin, if not provided, applies to all sessions |
|
|
749
742
|
|
|
750
743
|
|
|
744
|
+
#### CallMember
|
|
745
|
+
|
|
746
|
+
| Prop | Type | Description |
|
|
747
|
+
| -------------- | ------------------- | ----------------------------- |
|
|
748
|
+
| **`userId`** | <code>string</code> | User ID of the member |
|
|
749
|
+
| **`name`** | <code>string</code> | Display name of the user |
|
|
750
|
+
| **`imageURL`** | <code>string</code> | Profile image URL of the user |
|
|
751
|
+
| **`role`** | <code>string</code> | Role of the user in the call |
|
|
752
|
+
|
|
753
|
+
|
|
751
754
|
#### IncomingCallPayload
|
|
752
755
|
|
|
753
|
-
| Prop
|
|
754
|
-
|
|
|
755
|
-
| **`cid`**
|
|
756
|
-
| **`type`**
|
|
756
|
+
| Prop | Type | Description |
|
|
757
|
+
| ------------ | ------------------------------------------------- | ---------------------------------------- |
|
|
758
|
+
| **`cid`** | <code>string</code> | Full call CID (e.g. default:123) |
|
|
759
|
+
| **`type`** | <code>'incoming'</code> | Event type (currently always "incoming") |
|
|
760
|
+
| **`caller`** | <code><a href="#callmember">CallMember</a></code> | Information about the caller |
|
|
757
761
|
|
|
758
762
|
|
|
759
763
|
#### CameraEnabledResponse
|
|
@@ -63,6 +63,7 @@ import io.getstream.video.android.core.CameraDirection
|
|
|
63
63
|
import android.content.BroadcastReceiver
|
|
64
64
|
import android.content.Intent
|
|
65
65
|
import android.content.IntentFilter
|
|
66
|
+
import com.getcapacitor.JSArray
|
|
66
67
|
import io.getstream.android.video.generated.models.CallSessionParticipantLeftEvent
|
|
67
68
|
import io.getstream.video.android.core.RealtimeConnection
|
|
68
69
|
import io.getstream.video.android.core.events.ParticipantLeftEvent
|
|
@@ -215,15 +216,37 @@ public class StreamCallPlugin : Plugin() {
|
|
|
215
216
|
// Start ringtone only; UI handled in web layer
|
|
216
217
|
ringtonePlayer?.startRinging()
|
|
217
218
|
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
// Try to get caller information from the call
|
|
220
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
221
|
+
try {
|
|
222
|
+
val callInfo = call?.get()
|
|
223
|
+
val callerInfo = callInfo?.getOrNull()?.call?.createdBy
|
|
224
|
+
|
|
225
|
+
val payload = com.getcapacitor.JSObject().apply {
|
|
226
|
+
put("cid", cid.cid)
|
|
227
|
+
put("type", "incoming")
|
|
228
|
+
if (callerInfo != null) {
|
|
229
|
+
val caller = com.getcapacitor.JSObject().apply {
|
|
230
|
+
put("userId", callerInfo.id)
|
|
231
|
+
put("name", callerInfo.name ?: "")
|
|
232
|
+
put("imageURL", callerInfo.image ?: "")
|
|
233
|
+
put("role", callerInfo.role ?: "")
|
|
234
|
+
}
|
|
235
|
+
put("caller", caller)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Notify WebView/JS about incoming call so it can render its own UI
|
|
240
|
+
notifyListeners("incomingCall", payload, true)
|
|
241
|
+
} catch (e: Exception) {
|
|
242
|
+
android.util.Log.e("StreamCallPlugin", "Error getting call info for incoming call", e)
|
|
243
|
+
// Fallback to basic payload without caller info
|
|
244
|
+
val payload = com.getcapacitor.JSObject().apply {
|
|
245
|
+
put("cid", cid.cid)
|
|
246
|
+
put("type", "incoming")
|
|
247
|
+
}
|
|
248
|
+
notifyListeners("incomingCall", payload, true)
|
|
223
249
|
}
|
|
224
|
-
notifyListeners("incomingCall", payload, true)
|
|
225
|
-
} catch (e: Exception) {
|
|
226
|
-
android.util.Log.e("StreamCallPlugin", "Error notifying JS about incoming call", e)
|
|
227
250
|
}
|
|
228
251
|
|
|
229
252
|
bringAppToForeground()
|
|
@@ -602,7 +625,38 @@ public class StreamCallPlugin : Plugin() {
|
|
|
602
625
|
android.util.Log.v("StreamCallPlugin", "Received an event ${event.getEventType()} $event")
|
|
603
626
|
when (event) {
|
|
604
627
|
is CallRingEvent -> {
|
|
605
|
-
|
|
628
|
+
// Extract caller information from the ringing call
|
|
629
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
630
|
+
try {
|
|
631
|
+
val callCid = event.callCid
|
|
632
|
+
val callIdParts = callCid.split(":")
|
|
633
|
+
if (callIdParts.size >= 2) {
|
|
634
|
+
val callType = callIdParts[0]
|
|
635
|
+
val callId = callIdParts[1]
|
|
636
|
+
val call = streamVideoClient?.call(type = callType, id = callId)
|
|
637
|
+
val callInfo = call?.get()
|
|
638
|
+
val callerInfo = callInfo?.getOrNull()?.call?.createdBy
|
|
639
|
+
|
|
640
|
+
// Pass caller information to the ringing event
|
|
641
|
+
if (callerInfo != null) {
|
|
642
|
+
val caller = mapOf(
|
|
643
|
+
"userId" to callerInfo.id,
|
|
644
|
+
"name" to (callerInfo.name ?: ""),
|
|
645
|
+
"imageURL" to (callerInfo.image ?: ""),
|
|
646
|
+
"role" to (callerInfo.role ?: "")
|
|
647
|
+
)
|
|
648
|
+
updateCallStatusAndNotify(event.callCid, "ringing", null, null, null, caller)
|
|
649
|
+
} else {
|
|
650
|
+
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
654
|
+
}
|
|
655
|
+
} catch (e: Exception) {
|
|
656
|
+
android.util.Log.e("StreamCallPlugin", "Error getting caller info for ringing event", e)
|
|
657
|
+
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
658
|
+
}
|
|
659
|
+
}
|
|
606
660
|
}
|
|
607
661
|
// Handle CallCreatedEvent differently - only log it but don't try to access members yet
|
|
608
662
|
is CallCreatedEvent -> {
|
|
@@ -611,22 +665,59 @@ public class StreamCallPlugin : Plugin() {
|
|
|
611
665
|
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: All members from event: ${event.members.joinToString { it.user.id + " (role: " + it.user.role + ")" }}")
|
|
612
666
|
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: Self user ID from SDK: ${this@StreamCallPlugin.streamVideoClient?.userId}")
|
|
613
667
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
668
|
+
// Only send "created" event for outgoing calls (calls created by current user)
|
|
669
|
+
// For incoming calls, we'll only send "ringing" event in CallRingEvent handler
|
|
670
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
671
|
+
try {
|
|
672
|
+
val callIdParts = callCid.split(":")
|
|
673
|
+
if (callIdParts.size >= 2) {
|
|
674
|
+
val callType = callIdParts[0]
|
|
675
|
+
val callId = callIdParts[1]
|
|
676
|
+
val call = streamVideoClient?.call(type = callType, id = callId)
|
|
677
|
+
val callInfo = call?.get()
|
|
678
|
+
val createdBy = callInfo?.getOrNull()?.call?.createdBy
|
|
679
|
+
val currentUserId = streamVideoClient?.userId
|
|
680
|
+
|
|
681
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: Call created by: ${createdBy?.id}, Current user: $currentUserId")
|
|
682
|
+
|
|
683
|
+
// Only notify for outgoing calls (where current user is the creator)
|
|
684
|
+
if (createdBy?.id == currentUserId) {
|
|
685
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: This is an outgoing call, sending created event")
|
|
686
|
+
|
|
687
|
+
val callParticipants = event.members.filter {
|
|
688
|
+
val selfId = this@StreamCallPlugin.streamVideoClient?.userId
|
|
689
|
+
val memberId = it.user.id
|
|
690
|
+
val isSelf = memberId == selfId
|
|
691
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: Filtering member $memberId. Self ID: $selfId. Is self: $isSelf")
|
|
692
|
+
!isSelf
|
|
693
|
+
}.map { it.user.id }
|
|
694
|
+
|
|
695
|
+
android.util.Log.d("StreamCallPlugin", "Call created for $callCid with ${callParticipants.size} remote participants: ${callParticipants.joinToString()}.")
|
|
696
|
+
|
|
697
|
+
// Start tracking this call now that we have the member list
|
|
698
|
+
startCallTimeoutMonitor(callCid, callParticipants)
|
|
699
|
+
|
|
700
|
+
// Extract all members information (including self) for UI display
|
|
701
|
+
val allMembers = event.members.map { member ->
|
|
702
|
+
mapOf(
|
|
703
|
+
"userId" to member.user.id,
|
|
704
|
+
"name" to (member.user.name ?: ""),
|
|
705
|
+
"imageURL" to (member.user.image ?: ""),
|
|
706
|
+
"role" to (member.user.role ?: "")
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
updateCallStatusAndNotify(callCid, "created", null, null, allMembers)
|
|
711
|
+
} else {
|
|
712
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: This is an incoming call (created by ${createdBy?.id}), not sending created event")
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
android.util.Log.w("StreamCallPlugin", "CallCreatedEvent: Invalid call CID format: $callCid")
|
|
716
|
+
}
|
|
717
|
+
} catch (e: Exception) {
|
|
718
|
+
android.util.Log.e("StreamCallPlugin", "Error processing CallCreatedEvent", e)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
630
721
|
}
|
|
631
722
|
// Add handler for CallSessionStartedEvent which contains participant information
|
|
632
723
|
is CallSessionStartedEvent -> {
|
|
@@ -1153,7 +1244,35 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1153
1244
|
private suspend fun endCallRaw(call: Call) {
|
|
1154
1245
|
val callId = call.id
|
|
1155
1246
|
android.util.Log.d("StreamCallPlugin", "Attempting to end call $callId")
|
|
1156
|
-
|
|
1247
|
+
|
|
1248
|
+
try {
|
|
1249
|
+
// Get call information to make the decision
|
|
1250
|
+
val callInfo = call.get()
|
|
1251
|
+
val callData = callInfo?.getOrNull()?.call
|
|
1252
|
+
val currentUserId = streamVideoClient?.userId
|
|
1253
|
+
val createdBy = callData?.createdBy?.id
|
|
1254
|
+
val isCreator = createdBy == currentUserId
|
|
1255
|
+
|
|
1256
|
+
// Use call.state.totalParticipants to get participant count (as per StreamVideo Android SDK docs)
|
|
1257
|
+
val totalParticipants = call.state.totalParticipants.value ?: 0
|
|
1258
|
+
val shouldEndCall = isCreator || totalParticipants <= 2
|
|
1259
|
+
|
|
1260
|
+
android.util.Log.d("StreamCallPlugin", "Call $callId - Creator: $createdBy, CurrentUser: $currentUserId, IsCreator: $isCreator, TotalParticipants: $totalParticipants, ShouldEnd: $shouldEndCall")
|
|
1261
|
+
|
|
1262
|
+
if (shouldEndCall) {
|
|
1263
|
+
// End the call for everyone if I'm the creator or only 2 people
|
|
1264
|
+
android.util.Log.d("StreamCallPlugin", "Ending call $callId for all participants (creator: $isCreator, participants: $totalParticipants)")
|
|
1265
|
+
call.end()
|
|
1266
|
+
} else {
|
|
1267
|
+
// Just leave the call if there are more than 2 people and I'm not the creator
|
|
1268
|
+
android.util.Log.d("StreamCallPlugin", "Leaving call $callId (not creator, >2 participants)")
|
|
1269
|
+
call.leave()
|
|
1270
|
+
}
|
|
1271
|
+
} catch (e: Exception) {
|
|
1272
|
+
android.util.Log.e("StreamCallPlugin", "Error getting call info for $callId, defaulting to leave()", e)
|
|
1273
|
+
// Fallback to leave if we can't determine the call info
|
|
1274
|
+
call.leave()
|
|
1275
|
+
}
|
|
1157
1276
|
|
|
1158
1277
|
// Capture context from the overlayView
|
|
1159
1278
|
val currentContext = overlayView?.context ?: this.savedContext
|
|
@@ -1250,16 +1369,22 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1250
1369
|
@PluginMethod
|
|
1251
1370
|
fun endCall(call: PluginCall) {
|
|
1252
1371
|
try {
|
|
1253
|
-
val activeCall = streamVideoClient?.state?.activeCall
|
|
1254
|
-
|
|
1255
|
-
|
|
1372
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1373
|
+
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1374
|
+
|
|
1375
|
+
val callToEnd = activeCall ?: ringingCall
|
|
1376
|
+
|
|
1377
|
+
if (callToEnd == null) {
|
|
1378
|
+
android.util.Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1256
1379
|
call.reject("No active call to end")
|
|
1257
1380
|
return
|
|
1258
1381
|
}
|
|
1259
1382
|
|
|
1383
|
+
android.util.Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1384
|
+
|
|
1260
1385
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1261
1386
|
try {
|
|
1262
|
-
|
|
1387
|
+
endCallRaw(callToEnd)
|
|
1263
1388
|
call.resolve(JSObject().apply {
|
|
1264
1389
|
put("success", true)
|
|
1265
1390
|
})
|
|
@@ -1587,7 +1712,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1587
1712
|
}
|
|
1588
1713
|
|
|
1589
1714
|
// Helper method to update call status and notify listeners
|
|
1590
|
-
private fun updateCallStatusAndNotify(callId: String, state: String, userId: String? = null, reason: String? = null) {
|
|
1715
|
+
private fun updateCallStatusAndNotify(callId: String, state: String, userId: String? = null, reason: String? = null, members: List<Map<String, Any>>? = null, caller: Map<String, Any>? = null) {
|
|
1591
1716
|
android.util.Log.d("StreamCallPlugin", "updateCallStatusAndNotify called: callId=$callId, state=$state, userId=$userId, reason=$reason")
|
|
1592
1717
|
// Update stored call info
|
|
1593
1718
|
currentCallId = callId
|
|
@@ -1608,6 +1733,26 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1608
1733
|
reason?.let {
|
|
1609
1734
|
put("reason", it)
|
|
1610
1735
|
}
|
|
1736
|
+
members?.let { membersList ->
|
|
1737
|
+
val membersArray = JSArray()
|
|
1738
|
+
membersList.forEach { member ->
|
|
1739
|
+
val memberObj = JSObject().apply {
|
|
1740
|
+
member.forEach { (key, value) ->
|
|
1741
|
+
put(key, value)
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
membersArray.put(memberObj)
|
|
1745
|
+
}
|
|
1746
|
+
put("members", membersArray)
|
|
1747
|
+
}
|
|
1748
|
+
caller?.let { callerInfo ->
|
|
1749
|
+
val callerObj = JSObject().apply {
|
|
1750
|
+
callerInfo.forEach { (key, value) ->
|
|
1751
|
+
put(key, value)
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
put("caller", callerObj)
|
|
1755
|
+
}
|
|
1611
1756
|
}
|
|
1612
1757
|
|
|
1613
1758
|
// Notify listeners
|