@capgo/capacitor-stream-call 0.0.43 → 0.0.51
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 +184 -33
- 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,18 +216,46 @@ 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
|
+
|
|
242
|
+
// Delay bringing app to foreground to allow the event to be processed first
|
|
243
|
+
kotlinx.coroutines.delay(500) // 500ms delay
|
|
244
|
+
bringAppToForeground()
|
|
245
|
+
} catch (e: Exception) {
|
|
246
|
+
android.util.Log.e("StreamCallPlugin", "Error getting call info for incoming call", e)
|
|
247
|
+
// Fallback to basic payload without caller info
|
|
248
|
+
val payload = com.getcapacitor.JSObject().apply {
|
|
249
|
+
put("cid", cid.cid)
|
|
250
|
+
put("type", "incoming")
|
|
251
|
+
}
|
|
252
|
+
notifyListeners("incomingCall", payload, true)
|
|
253
|
+
|
|
254
|
+
// Delay bringing app to foreground to allow the event to be processed first
|
|
255
|
+
kotlinx.coroutines.delay(500) // 500ms delay
|
|
256
|
+
bringAppToForeground()
|
|
223
257
|
}
|
|
224
|
-
notifyListeners("incomingCall", payload, true)
|
|
225
|
-
} catch (e: Exception) {
|
|
226
|
-
android.util.Log.e("StreamCallPlugin", "Error notifying JS about incoming call", e)
|
|
227
258
|
}
|
|
228
|
-
|
|
229
|
-
bringAppToForeground()
|
|
230
259
|
} else {
|
|
231
260
|
android.util.Log.w("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - cid is null. Cannot process.")
|
|
232
261
|
}
|
|
@@ -602,7 +631,38 @@ public class StreamCallPlugin : Plugin() {
|
|
|
602
631
|
android.util.Log.v("StreamCallPlugin", "Received an event ${event.getEventType()} $event")
|
|
603
632
|
when (event) {
|
|
604
633
|
is CallRingEvent -> {
|
|
605
|
-
|
|
634
|
+
// Extract caller information from the ringing call
|
|
635
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
636
|
+
try {
|
|
637
|
+
val callCid = event.callCid
|
|
638
|
+
val callIdParts = callCid.split(":")
|
|
639
|
+
if (callIdParts.size >= 2) {
|
|
640
|
+
val callType = callIdParts[0]
|
|
641
|
+
val callId = callIdParts[1]
|
|
642
|
+
val call = streamVideoClient?.call(type = callType, id = callId)
|
|
643
|
+
val callInfo = call?.get()
|
|
644
|
+
val callerInfo = callInfo?.getOrNull()?.call?.createdBy
|
|
645
|
+
|
|
646
|
+
// Pass caller information to the ringing event
|
|
647
|
+
if (callerInfo != null) {
|
|
648
|
+
val caller = mapOf(
|
|
649
|
+
"userId" to callerInfo.id,
|
|
650
|
+
"name" to (callerInfo.name ?: ""),
|
|
651
|
+
"imageURL" to (callerInfo.image ?: ""),
|
|
652
|
+
"role" to (callerInfo.role ?: "")
|
|
653
|
+
)
|
|
654
|
+
updateCallStatusAndNotify(event.callCid, "ringing", null, null, null, caller)
|
|
655
|
+
} else {
|
|
656
|
+
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
660
|
+
}
|
|
661
|
+
} catch (e: Exception) {
|
|
662
|
+
android.util.Log.e("StreamCallPlugin", "Error getting caller info for ringing event", e)
|
|
663
|
+
updateCallStatusAndNotify(event.callCid, "ringing")
|
|
664
|
+
}
|
|
665
|
+
}
|
|
606
666
|
}
|
|
607
667
|
// Handle CallCreatedEvent differently - only log it but don't try to access members yet
|
|
608
668
|
is CallCreatedEvent -> {
|
|
@@ -611,22 +671,59 @@ public class StreamCallPlugin : Plugin() {
|
|
|
611
671
|
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: All members from event: ${event.members.joinToString { it.user.id + " (role: " + it.user.role + ")" }}")
|
|
612
672
|
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: Self user ID from SDK: ${this@StreamCallPlugin.streamVideoClient?.userId}")
|
|
613
673
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
674
|
+
// Only send "created" event for outgoing calls (calls created by current user)
|
|
675
|
+
// For incoming calls, we'll only send "ringing" event in CallRingEvent handler
|
|
676
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
677
|
+
try {
|
|
678
|
+
val callIdParts = callCid.split(":")
|
|
679
|
+
if (callIdParts.size >= 2) {
|
|
680
|
+
val callType = callIdParts[0]
|
|
681
|
+
val callId = callIdParts[1]
|
|
682
|
+
val call = streamVideoClient?.call(type = callType, id = callId)
|
|
683
|
+
val callInfo = call?.get()
|
|
684
|
+
val createdBy = callInfo?.getOrNull()?.call?.createdBy
|
|
685
|
+
val currentUserId = streamVideoClient?.userId
|
|
686
|
+
|
|
687
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: Call created by: ${createdBy?.id}, Current user: $currentUserId")
|
|
688
|
+
|
|
689
|
+
// Only notify for outgoing calls (where current user is the creator)
|
|
690
|
+
if (createdBy?.id == currentUserId) {
|
|
691
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: This is an outgoing call, sending created event")
|
|
692
|
+
|
|
693
|
+
val callParticipants = event.members.filter {
|
|
694
|
+
val selfId = this@StreamCallPlugin.streamVideoClient?.userId
|
|
695
|
+
val memberId = it.user.id
|
|
696
|
+
val isSelf = memberId == selfId
|
|
697
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: Filtering member $memberId. Self ID: $selfId. Is self: $isSelf")
|
|
698
|
+
!isSelf
|
|
699
|
+
}.map { it.user.id }
|
|
700
|
+
|
|
701
|
+
android.util.Log.d("StreamCallPlugin", "Call created for $callCid with ${callParticipants.size} remote participants: ${callParticipants.joinToString()}.")
|
|
702
|
+
|
|
703
|
+
// Start tracking this call now that we have the member list
|
|
704
|
+
startCallTimeoutMonitor(callCid, callParticipants)
|
|
705
|
+
|
|
706
|
+
// Extract all members information (including self) for UI display
|
|
707
|
+
val allMembers = event.members.map { member ->
|
|
708
|
+
mapOf(
|
|
709
|
+
"userId" to member.user.id,
|
|
710
|
+
"name" to (member.user.name ?: ""),
|
|
711
|
+
"imageURL" to (member.user.image ?: ""),
|
|
712
|
+
"role" to (member.user.role ?: "")
|
|
713
|
+
)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
updateCallStatusAndNotify(callCid, "created", null, null, allMembers)
|
|
717
|
+
} else {
|
|
718
|
+
android.util.Log.d("StreamCallPlugin", "CallCreatedEvent: This is an incoming call (created by ${createdBy?.id}), not sending created event")
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
android.util.Log.w("StreamCallPlugin", "CallCreatedEvent: Invalid call CID format: $callCid")
|
|
722
|
+
}
|
|
723
|
+
} catch (e: Exception) {
|
|
724
|
+
android.util.Log.e("StreamCallPlugin", "Error processing CallCreatedEvent", e)
|
|
725
|
+
}
|
|
726
|
+
}
|
|
630
727
|
}
|
|
631
728
|
// Add handler for CallSessionStartedEvent which contains participant information
|
|
632
729
|
is CallSessionStartedEvent -> {
|
|
@@ -1153,7 +1250,35 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1153
1250
|
private suspend fun endCallRaw(call: Call) {
|
|
1154
1251
|
val callId = call.id
|
|
1155
1252
|
android.util.Log.d("StreamCallPlugin", "Attempting to end call $callId")
|
|
1156
|
-
|
|
1253
|
+
|
|
1254
|
+
try {
|
|
1255
|
+
// Get call information to make the decision
|
|
1256
|
+
val callInfo = call.get()
|
|
1257
|
+
val callData = callInfo?.getOrNull()?.call
|
|
1258
|
+
val currentUserId = streamVideoClient?.userId
|
|
1259
|
+
val createdBy = callData?.createdBy?.id
|
|
1260
|
+
val isCreator = createdBy == currentUserId
|
|
1261
|
+
|
|
1262
|
+
// Use call.state.totalParticipants to get participant count (as per StreamVideo Android SDK docs)
|
|
1263
|
+
val totalParticipants = call.state.totalParticipants.value ?: 0
|
|
1264
|
+
val shouldEndCall = isCreator || totalParticipants <= 2
|
|
1265
|
+
|
|
1266
|
+
android.util.Log.d("StreamCallPlugin", "Call $callId - Creator: $createdBy, CurrentUser: $currentUserId, IsCreator: $isCreator, TotalParticipants: $totalParticipants, ShouldEnd: $shouldEndCall")
|
|
1267
|
+
|
|
1268
|
+
if (shouldEndCall) {
|
|
1269
|
+
// End the call for everyone if I'm the creator or only 2 people
|
|
1270
|
+
android.util.Log.d("StreamCallPlugin", "Ending call $callId for all participants (creator: $isCreator, participants: $totalParticipants)")
|
|
1271
|
+
call.end()
|
|
1272
|
+
} else {
|
|
1273
|
+
// Just leave the call if there are more than 2 people and I'm not the creator
|
|
1274
|
+
android.util.Log.d("StreamCallPlugin", "Leaving call $callId (not creator, >2 participants)")
|
|
1275
|
+
call.leave()
|
|
1276
|
+
}
|
|
1277
|
+
} catch (e: Exception) {
|
|
1278
|
+
android.util.Log.e("StreamCallPlugin", "Error getting call info for $callId, defaulting to leave()", e)
|
|
1279
|
+
// Fallback to leave if we can't determine the call info
|
|
1280
|
+
call.leave()
|
|
1281
|
+
}
|
|
1157
1282
|
|
|
1158
1283
|
// Capture context from the overlayView
|
|
1159
1284
|
val currentContext = overlayView?.context ?: this.savedContext
|
|
@@ -1250,16 +1375,22 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1250
1375
|
@PluginMethod
|
|
1251
1376
|
fun endCall(call: PluginCall) {
|
|
1252
1377
|
try {
|
|
1253
|
-
val activeCall = streamVideoClient?.state?.activeCall
|
|
1254
|
-
|
|
1255
|
-
|
|
1378
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1379
|
+
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1380
|
+
|
|
1381
|
+
val callToEnd = activeCall ?: ringingCall
|
|
1382
|
+
|
|
1383
|
+
if (callToEnd == null) {
|
|
1384
|
+
android.util.Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1256
1385
|
call.reject("No active call to end")
|
|
1257
1386
|
return
|
|
1258
1387
|
}
|
|
1259
1388
|
|
|
1389
|
+
android.util.Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1390
|
+
|
|
1260
1391
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1261
1392
|
try {
|
|
1262
|
-
|
|
1393
|
+
endCallRaw(callToEnd)
|
|
1263
1394
|
call.resolve(JSObject().apply {
|
|
1264
1395
|
put("success", true)
|
|
1265
1396
|
})
|
|
@@ -1587,7 +1718,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1587
1718
|
}
|
|
1588
1719
|
|
|
1589
1720
|
// Helper method to update call status and notify listeners
|
|
1590
|
-
private fun updateCallStatusAndNotify(callId: String, state: String, userId: String? = null, reason: String? = null) {
|
|
1721
|
+
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
1722
|
android.util.Log.d("StreamCallPlugin", "updateCallStatusAndNotify called: callId=$callId, state=$state, userId=$userId, reason=$reason")
|
|
1592
1723
|
// Update stored call info
|
|
1593
1724
|
currentCallId = callId
|
|
@@ -1608,6 +1739,26 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1608
1739
|
reason?.let {
|
|
1609
1740
|
put("reason", it)
|
|
1610
1741
|
}
|
|
1742
|
+
members?.let { membersList ->
|
|
1743
|
+
val membersArray = JSArray()
|
|
1744
|
+
membersList.forEach { member ->
|
|
1745
|
+
val memberObj = JSObject().apply {
|
|
1746
|
+
member.forEach { (key, value) ->
|
|
1747
|
+
put(key, value)
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
membersArray.put(memberObj)
|
|
1751
|
+
}
|
|
1752
|
+
put("members", membersArray)
|
|
1753
|
+
}
|
|
1754
|
+
caller?.let { callerInfo ->
|
|
1755
|
+
val callerObj = JSObject().apply {
|
|
1756
|
+
callerInfo.forEach { (key, value) ->
|
|
1757
|
+
put(key, value)
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
put("caller", callerObj)
|
|
1761
|
+
}
|
|
1611
1762
|
}
|
|
1612
1763
|
|
|
1613
1764
|
// Notify listeners
|