@capgo/capacitor-stream-call 0.0.4 → 0.0.6
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.
|
@@ -206,22 +206,11 @@ fun CallOverlayView(
|
|
|
206
206
|
floatingVideoRenderer = floatingVideoRender
|
|
207
207
|
)
|
|
208
208
|
} else {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
color = VideoTheme.colors.basePrimary
|
|
215
|
-
)
|
|
216
|
-
} else {
|
|
217
|
-
Text(
|
|
218
|
-
modifier = Modifier.padding(30.dp),
|
|
219
|
-
text = "Join call ${call.id} in your browser to see the video here",
|
|
220
|
-
fontSize = 30.sp,
|
|
221
|
-
color = VideoTheme.colors.basePrimary,
|
|
222
|
-
textAlign = TextAlign.Center
|
|
223
|
-
)
|
|
224
|
-
}
|
|
209
|
+
Box(
|
|
210
|
+
modifier = Modifier
|
|
211
|
+
.fillMaxSize()
|
|
212
|
+
.background(VideoTheme.colors.baseSenary)
|
|
213
|
+
)
|
|
225
214
|
}
|
|
226
215
|
}
|
|
227
216
|
}
|
|
@@ -35,6 +35,7 @@ import io.getstream.video.android.model.StreamCallId
|
|
|
35
35
|
import io.getstream.video.android.model.User
|
|
36
36
|
import io.getstream.video.android.model.streamCallId
|
|
37
37
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
38
|
+
import kotlinx.coroutines.flow.Flow
|
|
38
39
|
import kotlinx.coroutines.launch
|
|
39
40
|
import org.openapitools.client.models.CallAcceptedEvent
|
|
40
41
|
import org.openapitools.client.models.CallEndedEvent
|
|
@@ -42,6 +43,8 @@ import org.openapitools.client.models.CallMissedEvent
|
|
|
42
43
|
import org.openapitools.client.models.CallRejectedEvent
|
|
43
44
|
import org.openapitools.client.models.CallSessionEndedEvent
|
|
44
45
|
import org.openapitools.client.models.VideoEvent
|
|
46
|
+
import io.getstream.video.android.model.Device
|
|
47
|
+
import kotlinx.coroutines.flow.first
|
|
45
48
|
|
|
46
49
|
// I am not a religious pearson, but at this point, I am not sure even god himself would understand this code
|
|
47
50
|
// It's a spaghetti-like, tangled, unreadable mess and frankly, I am deeply sorry for the code crimes commited in the Android impl
|
|
@@ -60,6 +63,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
60
63
|
private var savedActivity: Activity? = null
|
|
61
64
|
private var savedActivityPaused = false
|
|
62
65
|
private var savedCallsToEndOnResume = mutableListOf<Call>()
|
|
66
|
+
private val callStates: MutableMap<String, CallState> = mutableMapOf()
|
|
63
67
|
|
|
64
68
|
private enum class State {
|
|
65
69
|
NOT_INITIALIZED,
|
|
@@ -339,7 +343,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
339
343
|
SecureUserRepository.getInstance(context).save(credentials)
|
|
340
344
|
|
|
341
345
|
// Initialize Stream Video with new credentials
|
|
342
|
-
if (!hadSavedCredentials) {
|
|
346
|
+
if (!hadSavedCredentials || (savedCredentials!!.user.id != userId)) {
|
|
343
347
|
initializeStreamVideo()
|
|
344
348
|
}
|
|
345
349
|
|
|
@@ -358,15 +362,20 @@ public class StreamCallPlugin : Plugin() {
|
|
|
358
362
|
SecureUserRepository.getInstance(context).removeCurrentUser()
|
|
359
363
|
|
|
360
364
|
// Properly cleanup the client
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
365
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
366
|
+
streamVideoClient?.let {
|
|
367
|
+
magicDeviceDelete(it)
|
|
368
|
+
it.logOut()
|
|
369
|
+
StreamVideo.removeClient()
|
|
370
|
+
}
|
|
366
371
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
372
|
+
streamVideoClient = null
|
|
373
|
+
state = State.NOT_INITIALIZED
|
|
374
|
+
|
|
375
|
+
val ret = JSObject()
|
|
376
|
+
ret.put("success", true)
|
|
377
|
+
call.resolve(ret)
|
|
378
|
+
}
|
|
370
379
|
} catch (e: Exception) {
|
|
371
380
|
call.reject("Failed to logout", e)
|
|
372
381
|
}
|
|
@@ -552,89 +561,152 @@ public class StreamCallPlugin : Plugin() {
|
|
|
552
561
|
client.subscribe { event: VideoEvent ->
|
|
553
562
|
android.util.Log.v("StreamCallPlugin", "Received an event ${event.getEventType()} $event")
|
|
554
563
|
when (event) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
ringtonePlayer?.stopRinging()
|
|
568
|
-
// Hide incoming call view if visible
|
|
569
|
-
incomingCallView?.isVisible = false
|
|
570
|
-
}
|
|
564
|
+
// Handle CallCreatedEvent differently - only log it but don't try to access members yet
|
|
565
|
+
is org.openapitools.client.models.CallCreatedEvent -> {
|
|
566
|
+
val callCid = event.call.cid
|
|
567
|
+
android.util.Log.d("StreamCallPlugin", "Call created: $callCid")
|
|
568
|
+
|
|
569
|
+
// let's get the members
|
|
570
|
+
val callParticipants = event.members.filter{ it.user.id != this@StreamCallPlugin.streamVideoClient?.userId } .map { it.user.id }
|
|
571
|
+
android.util.Log.d("StreamCallPlugin", "Call created for $callCid with ${callParticipants.size} participants")
|
|
572
|
+
|
|
573
|
+
// Start tracking this call now that we have the member list
|
|
574
|
+
startCallTimeoutMonitor(callCid, callParticipants)
|
|
575
|
+
|
|
571
576
|
val data = JSObject().apply {
|
|
572
|
-
put("callId",
|
|
573
|
-
put("state", "
|
|
577
|
+
put("callId", callCid)
|
|
578
|
+
put("state", "created")
|
|
574
579
|
}
|
|
575
580
|
notifyListeners("callEvent", data)
|
|
576
581
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
CallOverlayView(
|
|
582
|
-
context = context,
|
|
583
|
-
streamVideo = streamVideoClient,
|
|
584
|
-
call = null
|
|
585
|
-
)
|
|
586
|
-
}
|
|
587
|
-
overlayView?.isVisible = false
|
|
588
|
-
// Stop ringtone if it's still playing
|
|
589
|
-
ringtonePlayer?.stopRinging()
|
|
590
|
-
// Hide incoming call view if visible
|
|
591
|
-
incomingCallView?.isVisible = false
|
|
592
|
-
}
|
|
582
|
+
// Add handler for CallSessionStartedEvent which contains participant information
|
|
583
|
+
is org.openapitools.client.models.CallSessionStartedEvent -> {
|
|
584
|
+
val callCid = event.call.cid
|
|
585
|
+
|
|
593
586
|
val data = JSObject().apply {
|
|
594
|
-
put("callId",
|
|
595
|
-
put("state", "
|
|
587
|
+
put("callId", callCid)
|
|
588
|
+
put("state", "session_started")
|
|
596
589
|
}
|
|
597
590
|
notifyListeners("callEvent", data)
|
|
598
591
|
}
|
|
592
|
+
|
|
599
593
|
is CallRejectedEvent -> {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
call = null
|
|
607
|
-
)
|
|
608
|
-
}
|
|
609
|
-
overlayView?.isVisible = false
|
|
610
|
-
|
|
611
|
-
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
612
|
-
if (keyguardManager.isKeyguardLocked) {
|
|
613
|
-
android.util.Log.d("StreamCallPlugin", "Stop ringing and move to background")
|
|
614
|
-
this@StreamCallPlugin.ringtonePlayer?.stopRinging()
|
|
615
|
-
moveAllActivitiesToBackgroundOrKill(context)
|
|
616
|
-
}
|
|
594
|
+
val userId = event.user.id
|
|
595
|
+
val callCid = event.call.cid
|
|
596
|
+
|
|
597
|
+
// Update call state
|
|
598
|
+
callStates[callCid]?.let { callState ->
|
|
599
|
+
callState.participantResponses[userId] = "rejected"
|
|
617
600
|
}
|
|
601
|
+
|
|
602
|
+
// runOnMainThread {
|
|
603
|
+
// android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallRejectedEvent for call ${event.call.cid}")
|
|
604
|
+
// overlayView?.setContent {
|
|
605
|
+
// CallOverlayView(
|
|
606
|
+
// context = context,
|
|
607
|
+
// streamVideo = streamVideoClient,
|
|
608
|
+
// call = null
|
|
609
|
+
// )
|
|
610
|
+
// }
|
|
611
|
+
// overlayView?.isVisible = false
|
|
612
|
+
// }
|
|
613
|
+
|
|
618
614
|
val data = JSObject().apply {
|
|
619
615
|
put("callId", event.call.cid)
|
|
620
616
|
put("state", "rejected")
|
|
617
|
+
put("userId", userId)
|
|
621
618
|
}
|
|
622
619
|
notifyListeners("callEvent", data)
|
|
620
|
+
|
|
621
|
+
// Check if all participants have responded
|
|
622
|
+
checkAllParticipantsResponded(callCid)
|
|
623
623
|
}
|
|
624
|
+
|
|
624
625
|
is CallMissedEvent -> {
|
|
626
|
+
val userId = event.user.id
|
|
627
|
+
val callCid = event.call.cid
|
|
628
|
+
|
|
629
|
+
// Update call state
|
|
630
|
+
callStates[callCid]?.let { callState ->
|
|
631
|
+
callState.participantResponses[userId] = "missed"
|
|
632
|
+
}
|
|
633
|
+
|
|
625
634
|
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
626
635
|
if (keyguardManager.isKeyguardLocked) {
|
|
627
636
|
android.util.Log.d("StreamCallPlugin", "Stop ringing and move to background")
|
|
628
637
|
this.ringtonePlayer?.stopRinging()
|
|
629
638
|
moveAllActivitiesToBackgroundOrKill(context)
|
|
630
639
|
}
|
|
640
|
+
|
|
641
|
+
val data = JSObject().apply {
|
|
642
|
+
put("callId", callCid)
|
|
643
|
+
put("state", "missed")
|
|
644
|
+
put("userId", userId)
|
|
645
|
+
}
|
|
646
|
+
notifyListeners("callEvent", data)
|
|
647
|
+
|
|
648
|
+
// Check if all participants have responded
|
|
649
|
+
checkAllParticipantsResponded(callCid)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
is CallAcceptedEvent -> {
|
|
653
|
+
val userId = event.user.id
|
|
654
|
+
val callCid = event.call.cid
|
|
655
|
+
|
|
656
|
+
// Update call state
|
|
657
|
+
callStates[callCid]?.let { callState ->
|
|
658
|
+
callState.participantResponses[userId] = "accepted"
|
|
659
|
+
|
|
660
|
+
// Since someone accepted, cancel the timeout timer
|
|
661
|
+
android.util.Log.d("StreamCallPlugin", "Call accepted by $userId, canceling timeout timer for $callCid")
|
|
662
|
+
callState.timer?.removeCallbacksAndMessages(null)
|
|
663
|
+
callState.timer = null
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
val data = JSObject().apply {
|
|
667
|
+
put("callId", callCid)
|
|
668
|
+
put("state", "accepted")
|
|
669
|
+
put("userId", userId)
|
|
670
|
+
}
|
|
671
|
+
notifyListeners("callEvent", data)
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
is CallEndedEvent -> {
|
|
675
|
+
runOnMainThread {
|
|
676
|
+
android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallEndedEvent for call ${event.call.cid}")
|
|
677
|
+
// Clean up call resources
|
|
678
|
+
val callCid = event.call.cid
|
|
679
|
+
cleanupCall(callCid)
|
|
680
|
+
}
|
|
681
|
+
val data = JSObject().apply {
|
|
682
|
+
put("callId", event.call.cid)
|
|
683
|
+
put("state", "left")
|
|
684
|
+
}
|
|
685
|
+
notifyListeners("callEvent", data)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
is CallSessionEndedEvent -> {
|
|
689
|
+
runOnMainThread {
|
|
690
|
+
android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallSessionEndedEvent for call ${event.call.cid}")
|
|
691
|
+
// Clean up call resources
|
|
692
|
+
val callCid = event.call.cid
|
|
693
|
+
cleanupCall(callCid)
|
|
694
|
+
}
|
|
695
|
+
val data = JSObject().apply {
|
|
696
|
+
put("callId", event.call.cid)
|
|
697
|
+
put("state", "left")
|
|
698
|
+
}
|
|
699
|
+
notifyListeners("callEvent", data)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
else -> {
|
|
703
|
+
val data = JSObject().apply {
|
|
704
|
+
put("callId", streamVideoClient?.state?.activeCall?.value?.cid)
|
|
705
|
+
put("state", event.getEventType())
|
|
706
|
+
}
|
|
707
|
+
notifyListeners("callEvent", data)
|
|
631
708
|
}
|
|
632
709
|
}
|
|
633
|
-
val data = JSObject().apply {
|
|
634
|
-
put("callId", streamVideoClient?.state?.activeCall?.value?.cid)
|
|
635
|
-
put("state", event.getEventType())
|
|
636
|
-
}
|
|
637
|
-
notifyListeners("callEvent", data)
|
|
638
710
|
}
|
|
639
711
|
|
|
640
712
|
// Add call state subscription using collect
|
|
@@ -1007,6 +1079,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1007
1079
|
val callType = call.getString("type") ?: "default"
|
|
1008
1080
|
val shouldRing = call.getBoolean("ring") ?: true
|
|
1009
1081
|
val callId = java.util.UUID.randomUUID().toString()
|
|
1082
|
+
val callCid = "$callType:$callId"
|
|
1010
1083
|
|
|
1011
1084
|
android.util.Log.d("StreamCallPlugin", "Creating call:")
|
|
1012
1085
|
android.util.Log.d("StreamCallPlugin", "- Call ID: $callId")
|
|
@@ -1019,59 +1092,10 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1019
1092
|
try {
|
|
1020
1093
|
// Create the call object
|
|
1021
1094
|
val streamCall = streamVideoClient?.call(type = callType, id = callId)
|
|
1022
|
-
|
|
1023
|
-
//
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
// Subscribe to call events for this specific call
|
|
1028
|
-
streamCall?.subscribe { event ->
|
|
1029
|
-
when (event) {
|
|
1030
|
-
is CallRejectedEvent -> {
|
|
1031
|
-
val userId = event.user.id
|
|
1032
|
-
android.util.Log.d("StreamCallPlugin", "Call was rejected by user: $userId")
|
|
1033
|
-
participantResponses[userId] = "rejected"
|
|
1034
|
-
|
|
1035
|
-
val data = JSObject().apply {
|
|
1036
|
-
put("callId", callId)
|
|
1037
|
-
put("state", "rejected")
|
|
1038
|
-
put("userId", userId)
|
|
1039
|
-
}
|
|
1040
|
-
notifyListeners("callEvent", data)
|
|
1041
|
-
|
|
1042
|
-
// Check if all participants have rejected or missed
|
|
1043
|
-
checkAllParticipantsResponded(participantResponses, totalParticipants, streamCall)
|
|
1044
|
-
}
|
|
1045
|
-
is CallMissedEvent -> {
|
|
1046
|
-
val userId = event.user.id
|
|
1047
|
-
android.util.Log.d("StreamCallPlugin", "Call was missed by user: $userId")
|
|
1048
|
-
participantResponses[userId] = "missed"
|
|
1049
|
-
|
|
1050
|
-
val data = JSObject().apply {
|
|
1051
|
-
put("callId", callId)
|
|
1052
|
-
put("state", "missed")
|
|
1053
|
-
put("userId", userId)
|
|
1054
|
-
}
|
|
1055
|
-
notifyListeners("callEvent", data)
|
|
1056
|
-
|
|
1057
|
-
// Check if all participants have rejected or missed
|
|
1058
|
-
checkAllParticipantsResponded(participantResponses, totalParticipants, streamCall)
|
|
1059
|
-
}
|
|
1060
|
-
is CallAcceptedEvent -> {
|
|
1061
|
-
val userId = event.user.id
|
|
1062
|
-
android.util.Log.d("StreamCallPlugin", "Call was accepted by user: $userId")
|
|
1063
|
-
participantResponses[userId] = "accepted"
|
|
1064
|
-
|
|
1065
|
-
val data = JSObject().apply {
|
|
1066
|
-
put("callId", callId)
|
|
1067
|
-
put("state", "accepted")
|
|
1068
|
-
put("userId", userId)
|
|
1069
|
-
}
|
|
1070
|
-
notifyListeners("callEvent", data)
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1095
|
+
|
|
1096
|
+
// Note: We no longer start tracking here - we'll wait for CallSessionStartedEvent
|
|
1097
|
+
// instead, which contains the actual participant list
|
|
1098
|
+
|
|
1075
1099
|
android.util.Log.d("StreamCallPlugin", "Creating call with members...")
|
|
1076
1100
|
// Create the call with all members
|
|
1077
1101
|
streamCall?.create(
|
|
@@ -1107,41 +1131,229 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1107
1131
|
}
|
|
1108
1132
|
}
|
|
1109
1133
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1134
|
+
private fun startCallTimeoutMonitor(callCid: String, memberIds: List<String>) {
|
|
1135
|
+
val callState = CallState(members = memberIds)
|
|
1136
|
+
|
|
1137
|
+
val handler = Handler(Looper.getMainLooper())
|
|
1138
|
+
val timeoutRunnable = object : Runnable {
|
|
1139
|
+
override fun run() {
|
|
1140
|
+
checkCallTimeout(callCid)
|
|
1141
|
+
handler.postDelayed(this, 1000)
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
handler.postDelayed(timeoutRunnable, 1000)
|
|
1146
|
+
callState.timer = handler
|
|
1147
|
+
|
|
1148
|
+
callStates[callCid] = callState
|
|
1149
|
+
|
|
1150
|
+
android.util.Log.d("StreamCallPlugin", "Started timeout monitor for call $callCid with ${memberIds.size} members")
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
private fun checkCallTimeout(callCid: String) {
|
|
1154
|
+
val callState = callStates[callCid] ?: return
|
|
1155
|
+
|
|
1156
|
+
val now = System.currentTimeMillis()
|
|
1157
|
+
val elapsedSeconds = (now - callState.createdAt) / 1000
|
|
1158
|
+
|
|
1159
|
+
if (elapsedSeconds >= 30) {
|
|
1160
|
+
android.util.Log.d("StreamCallPlugin", "Call $callCid has timed out after $elapsedSeconds seconds")
|
|
1161
|
+
|
|
1162
|
+
val hasAccepted = callState.participantResponses.values.any { it == "accepted" }
|
|
1163
|
+
|
|
1164
|
+
if (!hasAccepted) {
|
|
1165
|
+
android.util.Log.d("StreamCallPlugin", "No one accepted call $callCid, marking all non-responders as missed")
|
|
1120
1166
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
)
|
|
1167
|
+
// First, remove the timer to prevent further callbacks
|
|
1168
|
+
callState.timer?.removeCallbacksAndMessages(null)
|
|
1169
|
+
callState.timer = null
|
|
1170
|
+
|
|
1171
|
+
callState.members.forEach { memberId ->
|
|
1172
|
+
if (memberId !in callState.participantResponses) {
|
|
1173
|
+
callState.participantResponses[memberId] = "missed"
|
|
1174
|
+
|
|
1175
|
+
val data = JSObject().apply {
|
|
1176
|
+
put("callId", callCid)
|
|
1177
|
+
put("state", "missed")
|
|
1178
|
+
put("userId", memberId)
|
|
1131
1179
|
}
|
|
1132
|
-
|
|
1180
|
+
notifyListeners("callEvent", data)
|
|
1133
1181
|
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
val callIdParts = callCid.split(":")
|
|
1185
|
+
if (callIdParts.size >= 2) {
|
|
1186
|
+
val callType = callIdParts[0]
|
|
1187
|
+
val callId = callIdParts[1]
|
|
1134
1188
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1189
|
+
streamVideoClient?.call(type = callType, id = callId)?.let { call ->
|
|
1190
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1191
|
+
try {
|
|
1192
|
+
// Use endCallRaw instead of manual cleanup
|
|
1193
|
+
endCallRaw(call)
|
|
1194
|
+
|
|
1195
|
+
// Clean up state - we don't need to do this in endCallRaw because we already did it here
|
|
1196
|
+
callStates.remove(callCid)
|
|
1197
|
+
|
|
1198
|
+
// Notify that call has ended
|
|
1199
|
+
val data = JSObject().apply {
|
|
1200
|
+
put("callId", callCid)
|
|
1201
|
+
put("state", "ended")
|
|
1202
|
+
put("reason", "timeout")
|
|
1203
|
+
}
|
|
1204
|
+
notifyListeners("callEvent", data)
|
|
1205
|
+
} catch (e: Exception) {
|
|
1206
|
+
android.util.Log.e("StreamCallPlugin", "Error ending timed out call", e)
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
private fun cleanupCall(callCid: String) {
|
|
1216
|
+
// Get the call state
|
|
1217
|
+
val callState = callStates[callCid]
|
|
1218
|
+
|
|
1219
|
+
if (callState != null) {
|
|
1220
|
+
// Ensure timer is properly canceled
|
|
1221
|
+
android.util.Log.d("StreamCallPlugin", "Stopping timer for call: $callCid")
|
|
1222
|
+
callState.timer?.removeCallbacksAndMessages(null)
|
|
1223
|
+
callState.timer = null
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Remove from callStates
|
|
1227
|
+
callStates.remove(callCid)
|
|
1228
|
+
|
|
1229
|
+
// Hide UI elements directly without setting content
|
|
1230
|
+
runOnMainThread {
|
|
1231
|
+
android.util.Log.d("StreamCallPlugin", "Hiding UI elements for call $callCid (one-time cleanup)")
|
|
1232
|
+
overlayView?.isVisible = false
|
|
1233
|
+
ringtonePlayer?.stopRinging()
|
|
1234
|
+
incomingCallView?.isVisible = false
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
android.util.Log.d("StreamCallPlugin", "Cleaned up resources for ended call: $callCid")
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private fun checkAllParticipantsResponded(callCid: String) {
|
|
1241
|
+
val callState = callStates[callCid] ?: return
|
|
1242
|
+
|
|
1243
|
+
val totalParticipants = callState.members.size
|
|
1244
|
+
val responseCount = callState.participantResponses.size
|
|
1245
|
+
|
|
1246
|
+
android.util.Log.d("StreamCallPlugin", "Checking responses for call $callCid: $responseCount / $totalParticipants")
|
|
1247
|
+
|
|
1248
|
+
val allResponded = responseCount >= totalParticipants
|
|
1249
|
+
val allRejectedOrMissed = allResponded &&
|
|
1250
|
+
callState.participantResponses.values.all { it == "rejected" || it == "missed" }
|
|
1251
|
+
|
|
1252
|
+
if (allResponded && allRejectedOrMissed) {
|
|
1253
|
+
android.util.Log.d("StreamCallPlugin", "All participants have rejected or missed the call $callCid")
|
|
1254
|
+
|
|
1255
|
+
// Cancel the timer immediately to prevent further callbacks
|
|
1256
|
+
callState.timer?.removeCallbacksAndMessages(null)
|
|
1257
|
+
callState.timer = null
|
|
1258
|
+
|
|
1259
|
+
// End the call using endCallRaw
|
|
1260
|
+
val callIdParts = callCid.split(":")
|
|
1261
|
+
if (callIdParts.size >= 2) {
|
|
1262
|
+
val callType = callIdParts[0]
|
|
1263
|
+
val callId = callIdParts[1]
|
|
1264
|
+
|
|
1265
|
+
streamVideoClient?.call(type = callType, id = callId)?.let { call ->
|
|
1266
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1267
|
+
try {
|
|
1268
|
+
// Use endCallRaw instead of manual cleanup
|
|
1269
|
+
endCallRaw(call)
|
|
1270
|
+
|
|
1271
|
+
// Clean up state - we don't need to do this in endCallRaw because we already did it here
|
|
1272
|
+
callStates.remove(callCid)
|
|
1273
|
+
|
|
1274
|
+
// Notify that call has ended
|
|
1275
|
+
val data = JSObject().apply {
|
|
1276
|
+
put("callId", callCid)
|
|
1277
|
+
put("state", "ended")
|
|
1278
|
+
put("reason", "all_rejected_or_missed")
|
|
1279
|
+
}
|
|
1280
|
+
notifyListeners("callEvent", data)
|
|
1281
|
+
} catch (e: Exception) {
|
|
1282
|
+
android.util.Log.e("StreamCallPlugin", "Error ending call after all rejected/missed", e)
|
|
1283
|
+
}
|
|
1139
1284
|
}
|
|
1140
|
-
notifyListeners("callEvent", data)
|
|
1141
1285
|
}
|
|
1142
|
-
} catch (e: Exception) {
|
|
1143
|
-
android.util.Log.e("StreamCallPlugin", "Error checking participant responses: ${e.message}")
|
|
1144
1286
|
}
|
|
1145
1287
|
}
|
|
1146
1288
|
}
|
|
1289
|
+
|
|
1290
|
+
private suspend fun magicDeviceDelete(streamVideoClient: StreamVideo) {
|
|
1291
|
+
try {
|
|
1292
|
+
android.util.Log.d("StreamCallPlugin", "Starting magicDeviceDelete reflection operation")
|
|
1293
|
+
|
|
1294
|
+
// Get the streamNotificationManager field from StreamVideo
|
|
1295
|
+
val streamVideoClass = streamVideoClient.javaClass
|
|
1296
|
+
val notificationManagerField = streamVideoClass.getDeclaredField("streamNotificationManager")
|
|
1297
|
+
notificationManagerField.isAccessible = true
|
|
1298
|
+
val notificationManager = notificationManagerField.get(streamVideoClient)
|
|
1299
|
+
|
|
1300
|
+
if (notificationManager == null) {
|
|
1301
|
+
android.util.Log.e("StreamCallPlugin", "streamNotificationManager is null")
|
|
1302
|
+
return
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
android.util.Log.d("StreamCallPlugin", "Successfully accessed streamNotificationManager")
|
|
1306
|
+
|
|
1307
|
+
// Get deviceTokenStorage from notification manager
|
|
1308
|
+
val notificationManagerClass = notificationManager.javaClass
|
|
1309
|
+
val deviceTokenStorageField = notificationManagerClass.getDeclaredField("deviceTokenStorage")
|
|
1310
|
+
deviceTokenStorageField.isAccessible = true
|
|
1311
|
+
val deviceTokenStorage = deviceTokenStorageField.get(notificationManager)
|
|
1312
|
+
|
|
1313
|
+
if (deviceTokenStorage == null) {
|
|
1314
|
+
android.util.Log.e("StreamCallPlugin", "deviceTokenStorage is null")
|
|
1315
|
+
return
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
android.util.Log.d("StreamCallPlugin", "Successfully accessed deviceTokenStorage")
|
|
1319
|
+
|
|
1320
|
+
// Access the DeviceTokenStorage object dynamically without hardcoding class
|
|
1321
|
+
val deviceTokenStorageClass = deviceTokenStorage.javaClass
|
|
1322
|
+
|
|
1323
|
+
// Get the userDevice Flow from deviceTokenStorage
|
|
1324
|
+
val userDeviceField = deviceTokenStorageClass.getDeclaredField("userDevice")
|
|
1325
|
+
userDeviceField.isAccessible = true
|
|
1326
|
+
val userDeviceFlow = userDeviceField.get(deviceTokenStorage)
|
|
1327
|
+
|
|
1328
|
+
if (userDeviceFlow == null) {
|
|
1329
|
+
android.util.Log.e("StreamCallPlugin", "userDevice Flow is null")
|
|
1330
|
+
return
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
android.util.Log.d("StreamCallPlugin", "Successfully accessed userDevice Flow: $userDeviceFlow")
|
|
1334
|
+
|
|
1335
|
+
val castedUserDeviceFlow = userDeviceFlow as Flow<Device?>
|
|
1336
|
+
try {
|
|
1337
|
+
castedUserDeviceFlow.first {
|
|
1338
|
+
if (it == null) {
|
|
1339
|
+
android.util.Log.d("StreamCallPlugin", "Device is null. Nothing to remove")
|
|
1340
|
+
return@first true;
|
|
1341
|
+
}
|
|
1342
|
+
streamVideoClient.deleteDevice(it)
|
|
1343
|
+
return@first true;
|
|
1344
|
+
}
|
|
1345
|
+
} catch (e: Throwable) {
|
|
1346
|
+
android.util.Log.e("StreamCallPlugin", "Cannot collect flow in magicDeviceDelete", e)
|
|
1347
|
+
}
|
|
1348
|
+
} catch (e: Exception) {
|
|
1349
|
+
android.util.Log.e("StreamCallPlugin", "Error in magicDeviceDelete", e)
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
data class CallState(
|
|
1354
|
+
val members: List<String>,
|
|
1355
|
+
val participantResponses: MutableMap<String, String> = mutableMapOf(),
|
|
1356
|
+
val createdAt: Long = System.currentTimeMillis(),
|
|
1357
|
+
var timer: Handler? = null
|
|
1358
|
+
)
|
|
1147
1359
|
}
|
package/package.json
CHANGED