@capgo/capacitor-stream-call 0.0.65 → 0.0.67

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.
@@ -155,14 +155,42 @@ public class StreamCallPlugin : Plugin() {
155
155
  activity.startService(serviceIntent)
156
156
  android.util.Log.d("StreamCallPlugin", "Started StreamCallBackgroundService to keep app alive")
157
157
 
158
- // Handle initial intent if present
159
- activity?.intent?.let { handleOnNewIntent(it) }
160
-
161
- // process the very first intent that started the app (if any)
162
- pendingIntent?.let {
163
- android.util.Log.d("StreamCallPlugin","Processing saved initial intent")
164
- handleOnNewIntent(it)
165
- pendingIntent = null
158
+ // Handle intents, but avoid processing the same intent twice
159
+ val currentIntent = activity?.intent
160
+ val savedIntent = pendingIntent
161
+
162
+ // Check if both intents are the same (common when app is killed and restarted)
163
+ val areSameIntent = currentIntent != null && savedIntent != null &&
164
+ currentIntent.action == savedIntent.action &&
165
+ try {
166
+ val currentCid = currentIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
167
+ val savedCid = savedIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
168
+ currentCid?.cid == savedCid?.cid
169
+ } catch (e: Exception) {
170
+ android.util.Log.w("StreamCallPlugin", "Error comparing call CIDs: ${e.message}")
171
+ false
172
+ }
173
+
174
+ when {
175
+ areSameIntent -> {
176
+ android.util.Log.d("StreamCallPlugin", "Current intent and saved intent are identical, processing only once")
177
+ if (currentIntent != null) {
178
+ handleOnNewIntent(currentIntent)
179
+ }
180
+ pendingIntent = null // Clear to prevent double processing
181
+ }
182
+ savedIntent != null -> {
183
+ android.util.Log.d("StreamCallPlugin", "Processing saved initial intent")
184
+ handleOnNewIntent(savedIntent)
185
+ pendingIntent = null
186
+ }
187
+ currentIntent != null -> {
188
+ android.util.Log.d("StreamCallPlugin", "Processing current activity intent")
189
+ handleOnNewIntent(currentIntent)
190
+ }
191
+ else -> {
192
+ android.util.Log.d("StreamCallPlugin", "No intents to process")
193
+ }
166
194
  }
167
195
  }
168
196
 
@@ -240,6 +268,12 @@ public class StreamCallPlugin : Plugin() {
240
268
  android.util.Log.d("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Accepting call with cid: $cid")
241
269
  val call = streamVideoClient?.call(id = cid.id, type = cid.type)
242
270
  if (call != null) {
271
+ // Log the full stack trace to see exactly where this is called from
272
+ val stackTrace = Thread.currentThread().stackTrace
273
+ android.util.Log.d("StreamCallPlugin", "internalAcceptCall STACK TRACE:")
274
+ stackTrace.forEachIndexed { index, element ->
275
+ android.util.Log.d("StreamCallPlugin", " [$index] ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
276
+ }
243
277
  kotlinx.coroutines.GlobalScope.launch {
244
278
  internalAcceptCall(call)
245
279
  }
@@ -255,13 +289,6 @@ public class StreamCallPlugin : Plugin() {
255
289
  android.util.Log.d("StreamCallPlugin", "New Intent - Extras: $extras")
256
290
  }
257
291
 
258
- // Public method to handle ACCEPT_CALL intent from MainActivity
259
- @JvmOverloads
260
- public fun handleAcceptCallIntent(intent: android.content.Intent) {
261
- android.util.Log.d("StreamCallPlugin", "handleAcceptCallIntent called: action=${intent.action}")
262
- handleOnNewIntent(intent)
263
- }
264
-
265
292
  @OptIn(DelicateCoroutinesApi::class)
266
293
  private fun declineCall(call: Call) {
267
294
  android.util.Log.d("StreamCallPlugin", "declineCall called for call: ${call.id}")
@@ -326,37 +353,64 @@ public class StreamCallPlugin : Plugin() {
326
353
  ViewGroup.LayoutParams.MATCH_PARENT,
327
354
  ViewGroup.LayoutParams.MATCH_PARENT
328
355
  )
329
- setContent {
330
- VideoTheme {
331
- val activeCall = streamVideoClient?.state?.activeCall?.collectAsState()?.value
332
- if (activeCall != null) {
333
- val participants by activeCall.state.participants.collectAsStateWithLifecycle()
334
- val sortedParticipants by activeCall.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
335
- val callParticipants by remember(participants) {
336
- derivedStateOf {
337
- if (sortedParticipants.size > 6) {
338
- sortedParticipants
339
- } else {
340
- participants
341
- }
356
+ }
357
+ parent.addView(overlayView, 0) // Add at index 0 to ensure it's below WebView
358
+
359
+ // Initialize with active call content
360
+ setOverlayContent()
361
+
362
+ // Create barrier view (above webview for blocking interaction during call setup)
363
+ barrierView = View(context).apply {
364
+ isVisible = false
365
+ layoutParams = FrameLayout.LayoutParams(
366
+ ViewGroup.LayoutParams.MATCH_PARENT,
367
+ ViewGroup.LayoutParams.MATCH_PARENT
368
+ )
369
+ setBackgroundColor(Color.parseColor("#1a242c"))
370
+ }
371
+ parent.addView(barrierView, parent.indexOfChild(bridge?.webView) + 1) // Add above WebView
372
+ }
373
+
374
+ /**
375
+ * Centralized function to set the overlay content with call UI.
376
+ * This handles all the common Compose UI setup for video calls.
377
+ */
378
+ private fun setOverlayContent(call: Call? = null) {
379
+ overlayView?.setContent {
380
+ VideoTheme {
381
+ val activeCall = call ?: streamVideoClient?.state?.activeCall?.collectAsState()?.value
382
+ if (activeCall != null) {
383
+ val participants by activeCall.state.participants.collectAsStateWithLifecycle()
384
+ val sortedParticipants by activeCall.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
385
+ val callParticipants by remember(participants) {
386
+ derivedStateOf {
387
+ if (sortedParticipants.size > 6) {
388
+ sortedParticipants
389
+ } else {
390
+ participants
342
391
  }
343
392
  }
393
+ }
344
394
 
345
- val currentLocal by activeCall.state.me.collectAsStateWithLifecycle()
346
-
347
- CallContent(
348
- call = activeCall,
349
- onBackPressed = { /* Handle back press if needed */ },
350
- videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
351
- ParticipantVideo(
352
- modifier = videoModifier,
353
- call = videoCall,
354
- participant = videoParticipant,
355
- style = videoStyle,
356
- actionsContent = {_, _, _ -> {}}
357
- )
358
- },
359
- floatingVideoRenderer = { call, parentSize ->
395
+ val currentLocal by activeCall.state.me.collectAsStateWithLifecycle()
396
+
397
+ CallContent(
398
+ call = activeCall,
399
+ onBackPressed = { /* Handle back press if needed */ },
400
+ controlsContent = { /* Empty to disable native controls */ },
401
+ appBarContent = { /* Empty to disable app bar with stop call button */ },
402
+ videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
403
+ ParticipantVideo(
404
+ modifier = videoModifier,
405
+ call = videoCall,
406
+ participant = videoParticipant,
407
+ style = videoStyle,
408
+ actionsContent = {_, _, _ -> {}},
409
+ scalingType = VideoScalingType.SCALE_ASPECT_FIT
410
+ )
411
+ },
412
+ floatingVideoRenderer = { call, parentSize ->
413
+ currentLocal?.let {
360
414
  FloatingParticipantVideo(
361
415
  call = call,
362
416
  participant = currentLocal!!,
@@ -368,30 +422,19 @@ public class StreamCallPlugin : Plugin() {
368
422
  .fillMaxSize()
369
423
  .clip(VideoTheme.shapes.dialog),
370
424
  call = call,
371
- participant = currentLocal!!,
425
+ participant = it,
372
426
  style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
373
427
  actionsContent = {_, _, _ -> {}},
374
428
  )
375
429
  }
376
430
  )
377
431
  }
378
- )
379
- }
432
+
433
+ }
434
+ )
380
435
  }
381
436
  }
382
437
  }
383
- parent.addView(overlayView, 0) // Add at index 0 to ensure it's below WebView
384
-
385
- // Create barrier view (above webview for blocking interaction during call setup)
386
- barrierView = View(context).apply {
387
- isVisible = false
388
- layoutParams = FrameLayout.LayoutParams(
389
- ViewGroup.LayoutParams.MATCH_PARENT,
390
- ViewGroup.LayoutParams.MATCH_PARENT
391
- )
392
- setBackgroundColor(Color.parseColor("#1a242c"))
393
- }
394
- parent.addView(barrierView, parent.indexOfChild(bridge?.webView) + 1) // Add above WebView
395
438
  }
396
439
 
397
440
  @PluginMethod
@@ -980,6 +1023,7 @@ public class StreamCallPlugin : Plugin() {
980
1023
  @OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
981
1024
  internal fun internalAcceptCall(call: Call) {
982
1025
  android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Entered for call: ${call.id}")
1026
+
983
1027
  kotlinx.coroutines.GlobalScope.launch {
984
1028
  try {
985
1029
  android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Coroutine started for call ${call.id}")
@@ -1033,65 +1077,8 @@ public class StreamCallPlugin : Plugin() {
1033
1077
  call.microphone?.setEnabled(true)
1034
1078
  call.camera?.setEnabled(true)
1035
1079
  android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Microphone and camera enabled for call ${call.id}")
1036
- overlayView?.setContent {
1037
- VideoTheme {
1038
- if (call != null) {
1039
- android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Setting CallContent with active call ${call.id}")
1040
-
1041
- val participants by call.state.participants.collectAsStateWithLifecycle()
1042
- val sortedParticipants by call.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
1043
- val callParticipants by remember(participants) {
1044
- derivedStateOf {
1045
- if (sortedParticipants.size > 6) {
1046
- sortedParticipants
1047
- } else {
1048
- participants
1049
- }
1050
- }
1051
- }
1052
-
1053
- val currentLocal by call.state.me.collectAsStateWithLifecycle()
1054
-
1055
- CallContent(
1056
- call = call,
1057
- onBackPressed = { /* ... */ },
1058
- controlsContent = { /* ... */ },
1059
- appBarContent = { /* ... */ },
1060
- videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
1061
- ParticipantVideo(
1062
- modifier = videoModifier,
1063
- call = videoCall,
1064
- participant = videoParticipant,
1065
- style = videoStyle,
1066
- actionsContent = {_, _, _ -> {}},
1067
- scalingType = VideoScalingType.SCALE_ASPECT_FIT
1068
- )
1069
- },
1070
- floatingVideoRenderer = { call, parentSize ->
1071
- FloatingParticipantVideo(
1072
- call = call,
1073
- participant = currentLocal!!,
1074
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1075
- parentBounds = parentSize,
1076
- videoRenderer = { _ ->
1077
- ParticipantVideo(
1078
- modifier = Modifier
1079
- .fillMaxSize()
1080
- .clip(VideoTheme.shapes.dialog),
1081
- call = call,
1082
- participant = currentLocal!!,
1083
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1084
- actionsContent = {_, _, _ ->},
1085
- )
1086
- }
1087
- )
1088
- }
1089
- )
1090
- } else {
1091
- android.util.Log.w("StreamCallPlugin", "internalAcceptCall: Active call is null, cannot set CallContent for call ${call.id}")
1092
- }
1093
- }
1094
- }
1080
+ android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Setting CallContent with active call ${call.id}")
1081
+ setOverlayContent(call)
1095
1082
  android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Content set for overlayView for call ${call.id}")
1096
1083
  overlayView?.isVisible = true
1097
1084
  android.util.Log.d("StreamCallPlugin", "internalAcceptCall: OverlayView set to visible for call ${call.id}, isVisible: ${overlayView?.isVisible}")
@@ -1111,61 +1098,8 @@ public class StreamCallPlugin : Plugin() {
1111
1098
  // Force refresh with active call from client
1112
1099
  val activeCall = streamVideoClient?.state?.activeCall?.value
1113
1100
  if (activeCall != null) {
1114
- overlayView?.setContent {
1115
- VideoTheme {
1116
- android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Force refreshing CallContent with active call ${activeCall.id}")
1117
-
1118
- val participants by activeCall.state.participants.collectAsStateWithLifecycle()
1119
- val sortedParticipants by activeCall.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
1120
- val callParticipants by remember(participants) {
1121
- derivedStateOf {
1122
- if (sortedParticipants.size > 6) {
1123
- sortedParticipants
1124
- } else {
1125
- participants
1126
- }
1127
- }
1128
- }
1129
-
1130
- val currentLocal by activeCall.state.me.collectAsStateWithLifecycle()
1131
-
1132
- CallContent(
1133
- call = activeCall,
1134
- onBackPressed = { /* ... */ },
1135
- controlsContent = { /* ... */ },
1136
- appBarContent = { /* ... */ },
1137
- videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
1138
- ParticipantVideo(
1139
- modifier = videoModifier,
1140
- call = videoCall,
1141
- participant = videoParticipant,
1142
- style = videoStyle,
1143
- actionsContent = {_, _, _ -> {}},
1144
- scalingType = VideoScalingType.SCALE_ASPECT_FIT
1145
- )
1146
- },
1147
- floatingVideoRenderer = { call, parentSize ->
1148
- FloatingParticipantVideo(
1149
- call = call,
1150
- participant = currentLocal!!,
1151
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1152
- parentBounds = parentSize,
1153
- videoRenderer = { _ ->
1154
- ParticipantVideo(
1155
- modifier = Modifier
1156
- .fillMaxSize()
1157
- .clip(VideoTheme.shapes.dialog),
1158
- call = call,
1159
- participant = currentLocal!!,
1160
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1161
- actionsContent = {_, _, _ -> {}},
1162
- )
1163
- }
1164
- )
1165
- }
1166
- )
1167
- }
1168
- }
1101
+ android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Force refreshing CallContent with active call ${activeCall.id}")
1102
+ setOverlayContent(activeCall)
1169
1103
  android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Content force refreshed for call ${activeCall.id}")
1170
1104
  } else {
1171
1105
  android.util.Log.w("StreamCallPlugin", "internalAcceptCall: Active call is null during force refresh for call ${call.id}")
@@ -1424,59 +1358,7 @@ public class StreamCallPlugin : Plugin() {
1424
1358
  return@runOnMainThread
1425
1359
  }
1426
1360
 
1427
- overlayView?.setContent {
1428
- VideoTheme {
1429
- val participants by call.state.participants.collectAsStateWithLifecycle()
1430
- val sortedParticipants by call.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
1431
- val callParticipants by remember(participants) {
1432
- derivedStateOf {
1433
- if (sortedParticipants.size > 6) {
1434
- sortedParticipants
1435
- } else {
1436
- participants
1437
- }
1438
- }
1439
- }
1440
-
1441
- val currentLocal by call.state.me.collectAsStateWithLifecycle()
1442
-
1443
- CallContent(
1444
- call = call,
1445
- onBackPressed = { /* Handle back press if needed */ },
1446
- controlsContent = { /* Empty to disable native controls */ },
1447
- appBarContent = { /* Empty to disable app bar with stop call button */ },
1448
- videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
1449
- ParticipantVideo(
1450
- modifier = videoModifier,
1451
- call = videoCall,
1452
- participant = videoParticipant,
1453
- style = videoStyle,
1454
- actionsContent = {_, _, _ -> {}},
1455
- scalingType = VideoScalingType.SCALE_ASPECT_FIT
1456
- )
1457
- },
1458
- floatingVideoRenderer = { call, parentSize ->
1459
- FloatingParticipantVideo(
1460
- call = call,
1461
- participant = currentLocal!!,
1462
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1463
- parentBounds = parentSize,
1464
- videoRenderer = { _ ->
1465
- ParticipantVideo(
1466
- modifier = Modifier
1467
- .fillMaxSize()
1468
- .clip(VideoTheme.shapes.dialog),
1469
- call = call,
1470
- participant = currentLocal!!,
1471
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1472
- actionsContent = {_, _, _ -> {}},
1473
- )
1474
- }
1475
- )
1476
- }
1477
- )
1478
- }
1479
- }
1361
+ setOverlayContent(call)
1480
1362
  overlayView?.isVisible = false
1481
1363
  bridge?.webView?.setBackgroundColor(Color.WHITE) // Restore webview opacity
1482
1364
 
@@ -1646,62 +1528,7 @@ public class StreamCallPlugin : Plugin() {
1646
1528
 
1647
1529
  bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
1648
1530
  bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
1649
- overlayView?.setContent {
1650
- VideoTheme {
1651
- if (streamCall != null) {
1652
-
1653
- val participants by streamCall.state.participants.collectAsStateWithLifecycle()
1654
- val sortedParticipants by streamCall.state.sortedParticipants.collectAsStateWithLifecycle(emptyList())
1655
- val callParticipants by remember(participants) {
1656
- derivedStateOf {
1657
- if (sortedParticipants.size > 6) {
1658
- sortedParticipants
1659
- } else {
1660
- participants
1661
- }
1662
- }
1663
- }
1664
-
1665
- val currentLocal by streamCall.state.me.collectAsStateWithLifecycle()
1666
-
1667
- CallContent(
1668
- call = streamCall,
1669
- onBackPressed = { /* Handle back press if needed */ },
1670
- controlsContent = { /* Empty to disable native controls */ },
1671
- appBarContent = { /* Empty to disable app bar with stop call button */ },
1672
- videoRenderer = { videoModifier, videoCall, videoParticipant, videoStyle ->
1673
- ParticipantVideo(
1674
- modifier = videoModifier,
1675
- call = videoCall,
1676
- participant = videoParticipant,
1677
- style = videoStyle,
1678
- actionsContent = {_, _, _ -> {}},
1679
- scalingType = VideoScalingType.SCALE_ASPECT_FIT
1680
- )
1681
- },
1682
- floatingVideoRenderer = { call, parentSize ->
1683
- FloatingParticipantVideo(
1684
- call = call,
1685
- participant = currentLocal!!,
1686
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1687
- parentBounds = parentSize,
1688
- videoRenderer = { _ ->
1689
- ParticipantVideo(
1690
- modifier = Modifier
1691
- .fillMaxSize()
1692
- .clip(VideoTheme.shapes.dialog),
1693
- call = call,
1694
- participant = currentLocal!!,
1695
- style = RegularVideoRendererStyle().copy(isShowingConnectionQualityIndicator = false),
1696
- actionsContent = {_, _, _ -> {}}
1697
- )
1698
- }
1699
- )
1700
- }
1701
- )
1702
- }
1703
- }
1704
- }
1531
+ setOverlayContent(streamCall)
1705
1532
  overlayView?.isVisible = true
1706
1533
  // Ensure overlay is behind WebView by adjusting its position in the parent
1707
1534
  val parent = overlayView?.parent as? ViewGroup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
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",