@capgo/capacitor-stream-call 0.0.70 → 0.0.71
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
CHANGED
|
@@ -787,7 +787,7 @@ The JSON representation for <a href="#listvalue">`ListValue`</a> is JSON array.
|
|
|
787
787
|
|
|
788
788
|
#### CallState
|
|
789
789
|
|
|
790
|
-
<code>'idle' | 'ringing' | 'joining' | 'reconnecting' | 'joined' | 'leaving' | 'left' | 'created' | 'session_started' | 'rejected' | 'missed' | 'accepted' | 'ended' | 'unknown'</code>
|
|
790
|
+
<code>'idle' | 'ringing' | 'joining' | 'reconnecting' | 'joined' | 'leaving' | 'left' | 'created' | 'session_started' | 'rejected' | 'missed' | 'accepted' | 'ended' | 'camera_enabled' | 'camera_disabled' | 'microphone_enabled' | 'microphone_disabled' | 'unknown'</code>
|
|
791
791
|
|
|
792
792
|
|
|
793
793
|
### Enums
|
|
@@ -3,6 +3,7 @@ package ee.forgr.capacitor.streamcall
|
|
|
3
3
|
import TouchInterceptWrapper
|
|
4
4
|
import android.Manifest
|
|
5
5
|
import android.app.Activity
|
|
6
|
+
import android.app.AlertDialog
|
|
6
7
|
import android.app.Application
|
|
7
8
|
import android.app.KeyguardManager
|
|
8
9
|
import android.content.BroadcastReceiver
|
|
@@ -17,12 +18,12 @@ import android.os.Build
|
|
|
17
18
|
import android.os.Bundle
|
|
18
19
|
import android.os.Handler
|
|
19
20
|
import android.os.Looper
|
|
21
|
+
import android.provider.Settings
|
|
20
22
|
import android.view.View
|
|
21
23
|
import android.view.ViewGroup
|
|
22
24
|
import android.view.WindowManager
|
|
23
25
|
import android.widget.FrameLayout
|
|
24
26
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
25
|
-
|
|
26
27
|
import androidx.compose.runtime.collectAsState
|
|
27
28
|
import androidx.compose.runtime.derivedStateOf
|
|
28
29
|
import androidx.compose.runtime.getValue
|
|
@@ -55,7 +56,6 @@ import io.getstream.android.video.generated.models.CallSessionEndedEvent
|
|
|
55
56
|
import io.getstream.android.video.generated.models.CallSessionParticipantLeftEvent
|
|
56
57
|
import io.getstream.android.video.generated.models.CallSessionStartedEvent
|
|
57
58
|
import io.getstream.android.video.generated.models.VideoEvent
|
|
58
|
-
import io.getstream.log.Priority
|
|
59
59
|
import io.getstream.video.android.compose.theme.VideoTheme
|
|
60
60
|
import io.getstream.video.android.compose.ui.components.call.activecall.CallContent
|
|
61
61
|
import io.getstream.video.android.compose.ui.components.call.renderer.FloatingParticipantVideo
|
|
@@ -68,18 +68,13 @@ import io.getstream.video.android.core.GEO
|
|
|
68
68
|
import io.getstream.video.android.core.RealtimeConnection
|
|
69
69
|
import io.getstream.video.android.core.StreamVideo
|
|
70
70
|
import io.getstream.video.android.core.StreamVideoBuilder
|
|
71
|
-
import io.getstream.video.android.core.call.CallType
|
|
72
71
|
import io.getstream.video.android.core.events.ParticipantLeftEvent
|
|
73
72
|
import io.getstream.video.android.core.internal.InternalStreamVideoApi
|
|
74
|
-
import io.getstream.video.android.core.logging.LoggingLevel
|
|
75
73
|
import io.getstream.video.android.core.notifications.NotificationConfig
|
|
76
74
|
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
77
|
-
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry
|
|
78
|
-
import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations
|
|
79
75
|
import io.getstream.video.android.core.sounds.RingingConfig
|
|
80
76
|
import io.getstream.video.android.core.sounds.toSounds
|
|
81
77
|
import io.getstream.video.android.model.Device
|
|
82
|
-
import io.getstream.video.android.model.StreamCallId
|
|
83
78
|
import io.getstream.video.android.model.User
|
|
84
79
|
import io.getstream.video.android.model.streamCallId
|
|
85
80
|
import kotlinx.coroutines.CoroutineScope
|
|
@@ -87,6 +82,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
|
|
87
82
|
import kotlinx.coroutines.Dispatchers
|
|
88
83
|
import kotlinx.coroutines.launch
|
|
89
84
|
import kotlinx.coroutines.tasks.await
|
|
85
|
+
import androidx.core.net.toUri
|
|
90
86
|
|
|
91
87
|
// I am not a religious pearson, but at this point, I am not sure even god himself would understand this code
|
|
92
88
|
// It's a spaghetti-like, tangled, unreadable mess and frankly, I am deeply sorry for the code crimes commited in the Android impl
|
|
@@ -114,6 +110,18 @@ public class StreamCallPlugin : Plugin() {
|
|
|
114
110
|
private var callFragment: StreamCallFragment? = null
|
|
115
111
|
private var streamVideo: StreamVideo? = null
|
|
116
112
|
private var touchInterceptWrapper: TouchInterceptWrapper? = null
|
|
113
|
+
|
|
114
|
+
// Track permission request timing and attempts
|
|
115
|
+
private var permissionRequestStartTime: Long = 0
|
|
116
|
+
private var permissionAttemptCount: Int = 0
|
|
117
|
+
|
|
118
|
+
// Store pending call information for permission handling
|
|
119
|
+
private var pendingCall: PluginCall? = null
|
|
120
|
+
private var pendingCallUserIds: List<String>? = null
|
|
121
|
+
private var pendingCallType: String? = null
|
|
122
|
+
private var pendingCallShouldRing: Boolean? = null
|
|
123
|
+
private var pendingCallTeam: String? = null
|
|
124
|
+
private var pendingAcceptCall: Call? = null // Store the actual call object for acceptance
|
|
117
125
|
|
|
118
126
|
private enum class State {
|
|
119
127
|
NOT_INITIALIZED,
|
|
@@ -136,6 +144,43 @@ public class StreamCallPlugin : Plugin() {
|
|
|
136
144
|
|
|
137
145
|
override fun handleOnResume() {
|
|
138
146
|
super.handleOnResume()
|
|
147
|
+
|
|
148
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: App resumed, checking permissions and pending operations")
|
|
149
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Have pendingCall: ${pendingCall != null}")
|
|
150
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Have pendingCallUserIds: ${pendingCallUserIds != null}")
|
|
151
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Have pendingAcceptCall: ${pendingAcceptCall != null}")
|
|
152
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Permission attempt count: $permissionAttemptCount")
|
|
153
|
+
|
|
154
|
+
// Check if permissions were granted after returning from settings or permission dialog
|
|
155
|
+
if (checkPermissions()) {
|
|
156
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Permissions are now granted")
|
|
157
|
+
// Handle any pending calls that were waiting for permissions
|
|
158
|
+
handlePermissionGranted()
|
|
159
|
+
} else if (pendingCall != null || pendingAcceptCall != null) {
|
|
160
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Permissions still not granted, but have pending operations")
|
|
161
|
+
// If we have pending operations but permissions are still not granted,
|
|
162
|
+
// it means the permission dialog was dismissed without granting
|
|
163
|
+
// We should trigger our retry logic if we haven't exhausted attempts
|
|
164
|
+
if (permissionAttemptCount > 0) {
|
|
165
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Permission dialog was dismissed, treating as denial (attempt: $permissionAttemptCount)")
|
|
166
|
+
val timeSinceRequest = System.currentTimeMillis() - permissionRequestStartTime
|
|
167
|
+
handlePermissionDenied(timeSinceRequest)
|
|
168
|
+
} else {
|
|
169
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: No permission attempts yet, starting permission request")
|
|
170
|
+
// If we have pending operations but no attempts yet, start the permission flow
|
|
171
|
+
if (pendingAcceptCall != null) {
|
|
172
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Have active call waiting for permissions, requesting now")
|
|
173
|
+
permissionAttemptCount = 0
|
|
174
|
+
requestPermissions()
|
|
175
|
+
} else if (pendingCall != null && pendingCallUserIds != null) {
|
|
176
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: Have outgoing call waiting for permissions, requesting now")
|
|
177
|
+
permissionAttemptCount = 0
|
|
178
|
+
requestPermissions()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
android.util.Log.d("StreamCallPlugin", "handleOnResume: No pending operations, nothing to handle")
|
|
183
|
+
}
|
|
139
184
|
}
|
|
140
185
|
|
|
141
186
|
override fun load() {
|
|
@@ -237,7 +282,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
237
282
|
android.util.Log.d("StreamCallPlugin", " [$index] ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
|
|
238
283
|
}
|
|
239
284
|
kotlinx.coroutines.GlobalScope.launch {
|
|
240
|
-
internalAcceptCall(call)
|
|
285
|
+
internalAcceptCall(call, requestPermissionsAfter = !checkPermissions())
|
|
241
286
|
}
|
|
242
287
|
bringAppToForeground()
|
|
243
288
|
} else {
|
|
@@ -633,7 +678,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
633
678
|
|
|
634
679
|
val intent = android.content.Intent(android.content.Intent.ACTION_MAIN).apply {
|
|
635
680
|
addCategory(android.content.Intent.CATEGORY_HOME)
|
|
636
|
-
flags =
|
|
681
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
637
682
|
}
|
|
638
683
|
context.startActivity(intent)
|
|
639
684
|
android.util.Log.d("StreamCallPlugin", "Moving app to background using HOME intent")
|
|
@@ -885,6 +930,22 @@ public class StreamCallPlugin : Plugin() {
|
|
|
885
930
|
updateCallStatusAndNotify(call.cid, "joined")
|
|
886
931
|
// Make sure activity is visible on lock screen
|
|
887
932
|
changeActivityAsVisibleOnLockScreen(this@StreamCallPlugin.activity, true)
|
|
933
|
+
|
|
934
|
+
// Listen to camera status changes
|
|
935
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
936
|
+
call.camera.isEnabled.collect { isEnabled ->
|
|
937
|
+
android.util.Log.d("StreamCallPlugin", "Camera status changed for call ${call.id}: enabled=$isEnabled")
|
|
938
|
+
updateCallStatusAndNotify(call.cid, if (isEnabled) "camera_enabled" else "camera_disabled")
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Listen to microphone status changes
|
|
943
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
944
|
+
call.microphone.isEnabled.collect { isEnabled ->
|
|
945
|
+
android.util.Log.d("StreamCallPlugin", "Microphone status changed for call ${call.id}: enabled=$isEnabled")
|
|
946
|
+
updateCallStatusAndNotify(call.cid, if (isEnabled) "microphone_enabled" else "microphone_disabled")
|
|
947
|
+
}
|
|
948
|
+
}
|
|
888
949
|
} ?: run {
|
|
889
950
|
// Notify that call has ended using our helper
|
|
890
951
|
updateCallStatusAndNotify("", "left")
|
|
@@ -955,8 +1016,20 @@ public class StreamCallPlugin : Plugin() {
|
|
|
955
1016
|
call.reject("Ringing call is null")
|
|
956
1017
|
return
|
|
957
1018
|
}
|
|
1019
|
+
|
|
1020
|
+
android.util.Log.d("StreamCallPlugin", "acceptCall: Accepting call immediately, will handle permissions after")
|
|
1021
|
+
|
|
1022
|
+
// Accept call immediately regardless of permissions - time is critical!
|
|
958
1023
|
kotlinx.coroutines.GlobalScope.launch {
|
|
959
|
-
|
|
1024
|
+
try {
|
|
1025
|
+
internalAcceptCall(streamVideoCall, requestPermissionsAfter = !checkPermissions())
|
|
1026
|
+
call.resolve(JSObject().apply {
|
|
1027
|
+
put("success", true)
|
|
1028
|
+
})
|
|
1029
|
+
} catch (e: Exception) {
|
|
1030
|
+
android.util.Log.e("StreamCallPlugin", "Error accepting call", e)
|
|
1031
|
+
call.reject("Failed to accept call: ${e.message}")
|
|
1032
|
+
}
|
|
960
1033
|
}
|
|
961
1034
|
} catch (t: Throwable) {
|
|
962
1035
|
android.util.Log.d("StreamCallPlugin", "JS -> acceptCall fail", t);
|
|
@@ -983,8 +1056,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
983
1056
|
}
|
|
984
1057
|
|
|
985
1058
|
@OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
|
|
986
|
-
internal fun internalAcceptCall(call: Call) {
|
|
987
|
-
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Entered for call: ${call.id}")
|
|
1059
|
+
internal fun internalAcceptCall(call: Call, requestPermissionsAfter: Boolean = false) {
|
|
1060
|
+
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Entered for call: ${call.id}, requestPermissionsAfter: $requestPermissionsAfter")
|
|
988
1061
|
|
|
989
1062
|
kotlinx.coroutines.GlobalScope.launch {
|
|
990
1063
|
try {
|
|
@@ -997,26 +1070,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
997
1070
|
}
|
|
998
1071
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Incoming call view hidden for call ${call.id}")
|
|
999
1072
|
|
|
1000
|
-
//
|
|
1001
|
-
|
|
1002
|
-
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: checkPermissions result for call ${call.id}: $permissionsGranted")
|
|
1003
|
-
if (!permissionsGranted) {
|
|
1004
|
-
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Permissions not granted for call ${call.id}. Requesting permissions.")
|
|
1005
|
-
requestPermissions()
|
|
1006
|
-
// Do not proceed with joining until permissions are granted
|
|
1007
|
-
runOnMainThread {
|
|
1008
|
-
android.widget.Toast.makeText(
|
|
1009
|
-
context,
|
|
1010
|
-
"Permissions required for call. Please grant them.",
|
|
1011
|
-
android.widget.Toast.LENGTH_LONG
|
|
1012
|
-
).show()
|
|
1013
|
-
}
|
|
1014
|
-
android.util.Log.w("StreamCallPlugin", "internalAcceptCall: Permissions not granted for call ${call.id}. Aborting accept process.")
|
|
1015
|
-
return@launch
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Permissions are granted for call ${call.id}. Proceeding to accept.")
|
|
1019
|
-
// Join the call without affecting others
|
|
1073
|
+
// Accept and join call immediately - don't wait for permissions!
|
|
1074
|
+
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Accepting call immediately for ${call.id}")
|
|
1020
1075
|
call.accept()
|
|
1021
1076
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: call.accept() completed for call ${call.id}")
|
|
1022
1077
|
call.join()
|
|
@@ -1035,10 +1090,15 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1035
1090
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: WebView background set to transparent for call ${call.id}")
|
|
1036
1091
|
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
1037
1092
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: WebView brought to front for call ${call.id}")
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
android.util.Log.d("StreamCallPlugin", "internalAcceptCall:
|
|
1093
|
+
|
|
1094
|
+
// Enable camera/microphone based on permissions
|
|
1095
|
+
val hasPermissions = checkPermissions()
|
|
1096
|
+
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Has permissions: $hasPermissions for call ${call.id}")
|
|
1097
|
+
|
|
1098
|
+
call.microphone?.setEnabled(hasPermissions)
|
|
1099
|
+
call.camera?.setEnabled(hasPermissions)
|
|
1100
|
+
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Microphone and camera set to $hasPermissions for call ${call.id}")
|
|
1101
|
+
|
|
1042
1102
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Setting CallContent with active call ${call.id}")
|
|
1043
1103
|
setOverlayContent(call)
|
|
1044
1104
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Content set for overlayView for call ${call.id}")
|
|
@@ -1050,6 +1110,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1050
1110
|
parent?.removeView(overlayView)
|
|
1051
1111
|
parent?.addView(overlayView, 0) // Add at index 0 to ensure it's behind other views
|
|
1052
1112
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: OverlayView re-added to parent at index 0 for call ${call.id}")
|
|
1113
|
+
|
|
1053
1114
|
// Add a small delay to ensure UI refresh
|
|
1054
1115
|
mainHandler.postDelayed({
|
|
1055
1116
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Delayed UI check, overlay visible: ${overlayView?.isVisible} for call ${call.id}")
|
|
@@ -1071,6 +1132,19 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1071
1132
|
}
|
|
1072
1133
|
}, 1000) // Increased delay to ensure all events are processed
|
|
1073
1134
|
}
|
|
1135
|
+
|
|
1136
|
+
// Request permissions after joining if needed
|
|
1137
|
+
if (requestPermissionsAfter) {
|
|
1138
|
+
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Requesting permissions after call acceptance for ${call.id}")
|
|
1139
|
+
runOnMainThread {
|
|
1140
|
+
// Store reference to the active call for enabling camera/mic later
|
|
1141
|
+
pendingAcceptCall = call
|
|
1142
|
+
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Set pendingAcceptCall to ${call.id}, resetting attempt count")
|
|
1143
|
+
permissionAttemptCount = 0
|
|
1144
|
+
requestPermissions()
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1074
1148
|
} catch (e: Exception) {
|
|
1075
1149
|
android.util.Log.e("StreamCallPlugin", "internalAcceptCall: Error accepting call ${call.id}: ${e.message}", e)
|
|
1076
1150
|
runOnMainThread {
|
|
@@ -1096,61 +1170,481 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1096
1170
|
return allGranted
|
|
1097
1171
|
}
|
|
1098
1172
|
|
|
1099
|
-
// Function to request required permissions
|
|
1100
|
-
private fun requestPermissions() {
|
|
1101
|
-
android.util.Log.d("StreamCallPlugin", "requestPermissions: Requesting RECORD_AUDIO and CAMERA permissions.")
|
|
1102
|
-
ActivityCompat.requestPermissions(
|
|
1103
|
-
activity,
|
|
1104
|
-
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA),
|
|
1105
|
-
1001 // Request code for permission result handling
|
|
1106
|
-
)
|
|
1107
|
-
android.util.Log.d("StreamCallPlugin", "requestPermissions: ActivityCompat.requestPermissions called.")
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
1173
|
// Override to handle permission results
|
|
1111
1174
|
override fun handleRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
1112
1175
|
super.handleRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
1113
|
-
android.util.Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Entered. RequestCode: $requestCode")
|
|
1114
|
-
|
|
1115
|
-
|
|
1176
|
+
android.util.Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Entered. RequestCode: $requestCode, Attempt: $permissionAttemptCount")
|
|
1177
|
+
android.util.Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Expected requestCode: 9001")
|
|
1178
|
+
|
|
1179
|
+
if (requestCode == 9001) {
|
|
1180
|
+
val responseTime = System.currentTimeMillis() - permissionRequestStartTime
|
|
1181
|
+
android.util.Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Response time: ${responseTime}ms")
|
|
1182
|
+
|
|
1116
1183
|
logPermissionResults(permissions, grantResults)
|
|
1184
|
+
|
|
1117
1185
|
if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
|
1118
1186
|
android.util.Log.i("StreamCallPlugin", "handleRequestPermissionsResult: All permissions GRANTED.")
|
|
1119
|
-
//
|
|
1187
|
+
// Reset attempt count on success
|
|
1188
|
+
permissionAttemptCount = 0
|
|
1189
|
+
handlePermissionGranted()
|
|
1190
|
+
} else {
|
|
1191
|
+
android.util.Log.e("StreamCallPlugin", "handleRequestPermissionsResult: Permissions DENIED. Attempt: $permissionAttemptCount")
|
|
1192
|
+
handlePermissionDenied(responseTime)
|
|
1193
|
+
}
|
|
1194
|
+
} else {
|
|
1195
|
+
android.util.Log.w("StreamCallPlugin", "handleRequestPermissionsResult: Received unknown requestCode: $requestCode")
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private fun logPermissionResults(permissions: Array<out String>, grantResults: IntArray) {
|
|
1200
|
+
android.util.Log.d("StreamCallPlugin", "logPermissionResults: Logging permission results:")
|
|
1201
|
+
for (i in permissions.indices) {
|
|
1202
|
+
val permission = permissions[i]
|
|
1203
|
+
val grantResult = if (grantResults.size > i) grantResults[i] else -999 // -999 for safety if arrays mismatch
|
|
1204
|
+
val resultString = if (grantResult == PackageManager.PERMISSION_GRANTED) "GRANTED" else "DENIED ($grantResult)"
|
|
1205
|
+
android.util.Log.d("StreamCallPlugin", " Permission: $permission, Result: $resultString")
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
private fun handlePermissionGranted() {
|
|
1210
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Processing granted permissions")
|
|
1211
|
+
|
|
1212
|
+
// Reset attempt count since permissions are now granted
|
|
1213
|
+
permissionAttemptCount = 0
|
|
1214
|
+
|
|
1215
|
+
// Determine what type of pending operation we have
|
|
1216
|
+
val hasOutgoingCall = pendingCall != null && pendingCallUserIds != null
|
|
1217
|
+
val hasActiveCallNeedingPermissions = pendingAcceptCall != null
|
|
1218
|
+
|
|
1219
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: hasOutgoingCall=$hasOutgoingCall, hasActiveCallNeedingPermissions=$hasActiveCallNeedingPermissions")
|
|
1220
|
+
|
|
1221
|
+
when {
|
|
1222
|
+
hasOutgoingCall -> {
|
|
1223
|
+
// Outgoing call creation was waiting for permissions
|
|
1224
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Executing pending outgoing call with ${pendingCallUserIds?.size} users")
|
|
1225
|
+
executePendingCall()
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
hasActiveCallNeedingPermissions -> {
|
|
1229
|
+
// Active call needing camera/microphone enabled
|
|
1230
|
+
val callToHandle = pendingAcceptCall!!
|
|
1231
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1232
|
+
|
|
1233
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Processing call ${callToHandle.id}")
|
|
1234
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Active call in state: ${activeCall?.id}")
|
|
1235
|
+
|
|
1236
|
+
if (activeCall != null && activeCall.id == callToHandle.id) {
|
|
1237
|
+
// Call is already active - enable camera/microphone
|
|
1238
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Enabling camera/microphone for active call ${callToHandle.id}")
|
|
1239
|
+
runOnMainThread {
|
|
1240
|
+
try {
|
|
1241
|
+
callToHandle.microphone?.setEnabled(true)
|
|
1242
|
+
callToHandle.camera?.setEnabled(true)
|
|
1243
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Camera and microphone enabled for call ${callToHandle.id}")
|
|
1244
|
+
|
|
1245
|
+
// Show success message
|
|
1246
|
+
android.widget.Toast.makeText(
|
|
1247
|
+
context,
|
|
1248
|
+
"Camera and microphone enabled",
|
|
1249
|
+
android.widget.Toast.LENGTH_SHORT
|
|
1250
|
+
).show()
|
|
1251
|
+
} catch (e: Exception) {
|
|
1252
|
+
android.util.Log.e("StreamCallPlugin", "Error enabling camera/microphone", e)
|
|
1253
|
+
}
|
|
1254
|
+
clearPendingCall()
|
|
1255
|
+
}
|
|
1256
|
+
} else if (pendingCall != null) {
|
|
1257
|
+
// Call not active yet - accept it (old flow, shouldn't happen with new flow)
|
|
1258
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Accepting pending incoming call ${callToHandle.id}")
|
|
1259
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1260
|
+
try {
|
|
1261
|
+
internalAcceptCall(callToHandle)
|
|
1262
|
+
pendingCall?.resolve(JSObject().apply {
|
|
1263
|
+
put("success", true)
|
|
1264
|
+
})
|
|
1265
|
+
} catch (e: Exception) {
|
|
1266
|
+
android.util.Log.e("StreamCallPlugin", "Error accepting call after permission grant", e)
|
|
1267
|
+
pendingCall?.reject("Failed to accept call: ${e.message}")
|
|
1268
|
+
} finally {
|
|
1269
|
+
clearPendingCall()
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
} else {
|
|
1273
|
+
// Just enable camera/mic for the stored call even if not currently active
|
|
1274
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Enabling camera/microphone for stored call ${callToHandle.id}")
|
|
1275
|
+
runOnMainThread {
|
|
1276
|
+
try {
|
|
1277
|
+
callToHandle.microphone?.setEnabled(true)
|
|
1278
|
+
callToHandle.camera?.setEnabled(true)
|
|
1279
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Camera and microphone enabled for stored call ${callToHandle.id}")
|
|
1280
|
+
|
|
1281
|
+
android.widget.Toast.makeText(
|
|
1282
|
+
context,
|
|
1283
|
+
"Camera and microphone enabled",
|
|
1284
|
+
android.widget.Toast.LENGTH_SHORT
|
|
1285
|
+
).show()
|
|
1286
|
+
} catch (e: Exception) {
|
|
1287
|
+
android.util.Log.e("StreamCallPlugin", "Error enabling camera/microphone for stored call", e)
|
|
1288
|
+
}
|
|
1289
|
+
clearPendingCall()
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
pendingCall != null -> {
|
|
1295
|
+
// We have a pending call but unclear what type - fallback handling
|
|
1296
|
+
android.util.Log.w("StreamCallPlugin", "handlePermissionGranted: Have pendingCall but unclear operation type")
|
|
1297
|
+
android.util.Log.w("StreamCallPlugin", " - pendingCallUserIds: ${pendingCallUserIds != null}")
|
|
1298
|
+
android.util.Log.w("StreamCallPlugin", " - pendingAcceptCall: ${pendingAcceptCall != null}")
|
|
1299
|
+
|
|
1300
|
+
// Try fallback to current ringing call for acceptance
|
|
1120
1301
|
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1121
|
-
android.util.Log.d("StreamCallPlugin", "handleRequestPermissionsResult: Ringing call object: ${ringingCall?.id}")
|
|
1122
1302
|
if (ringingCall != null) {
|
|
1123
|
-
android.util.Log.d("StreamCallPlugin", "
|
|
1303
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: Fallback - accepting current ringing call ${ringingCall.id}")
|
|
1124
1304
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1125
|
-
|
|
1305
|
+
try {
|
|
1306
|
+
internalAcceptCall(ringingCall)
|
|
1307
|
+
pendingCall?.resolve(JSObject().apply {
|
|
1308
|
+
put("success", true)
|
|
1309
|
+
})
|
|
1310
|
+
} catch (e: Exception) {
|
|
1311
|
+
android.util.Log.e("StreamCallPlugin", "Error accepting fallback call after permission grant", e)
|
|
1312
|
+
pendingCall?.reject("Failed to accept call: ${e.message}")
|
|
1313
|
+
} finally {
|
|
1314
|
+
clearPendingCall()
|
|
1315
|
+
}
|
|
1126
1316
|
}
|
|
1127
1317
|
} else {
|
|
1128
|
-
android.util.Log.w("StreamCallPlugin", "
|
|
1318
|
+
android.util.Log.w("StreamCallPlugin", "handlePermissionGranted: No ringing call found for fallback")
|
|
1319
|
+
pendingCall?.reject("Unable to determine pending operation")
|
|
1320
|
+
clearPendingCall()
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
else -> {
|
|
1325
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionGranted: No pending operations to handle")
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
private fun handlePermissionDenied(responseTime: Long) {
|
|
1331
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionDenied: Response time: ${responseTime}ms, Attempt: $permissionAttemptCount")
|
|
1332
|
+
|
|
1333
|
+
// Check if the response was instant (< 500ms) indicating "don't ask again"
|
|
1334
|
+
val instantDenial = responseTime < 500
|
|
1335
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionDenied: Instant denial detected: $instantDenial")
|
|
1336
|
+
|
|
1337
|
+
if (instantDenial) {
|
|
1338
|
+
// If it's an instant denial (don't ask again), go straight to settings dialog
|
|
1339
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionDenied: Instant denial, showing settings dialog")
|
|
1340
|
+
showPermissionSettingsDialog()
|
|
1341
|
+
} else if (permissionAttemptCount < 2) {
|
|
1342
|
+
// Try asking again immediately if this is the first denial
|
|
1343
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionDenied: First denial (attempt $permissionAttemptCount), asking again immediately")
|
|
1344
|
+
requestPermissions() // This will increment the attempt count
|
|
1345
|
+
} else {
|
|
1346
|
+
// Second denial - show settings dialog (final ask)
|
|
1347
|
+
android.util.Log.d("StreamCallPlugin", "handlePermissionDenied: Second denial (attempt $permissionAttemptCount), showing settings dialog (final ask)")
|
|
1348
|
+
showPermissionSettingsDialog()
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
private fun executePendingCall() {
|
|
1353
|
+
val call = pendingCall
|
|
1354
|
+
val userIds = pendingCallUserIds
|
|
1355
|
+
val callType = pendingCallType
|
|
1356
|
+
val shouldRing = pendingCallShouldRing
|
|
1357
|
+
val team = pendingCallTeam
|
|
1358
|
+
|
|
1359
|
+
if (call != null && userIds != null && callType != null && shouldRing != null) {
|
|
1360
|
+
android.util.Log.d("StreamCallPlugin", "executePendingCall: Executing call with ${userIds.size} users")
|
|
1361
|
+
|
|
1362
|
+
// Clear pending call data
|
|
1363
|
+
clearPendingCall()
|
|
1364
|
+
|
|
1365
|
+
// Execute the call creation logic
|
|
1366
|
+
createAndStartCall(call, userIds, callType, shouldRing, team)
|
|
1367
|
+
} else {
|
|
1368
|
+
android.util.Log.w("StreamCallPlugin", "executePendingCall: Missing pending call data")
|
|
1369
|
+
call?.reject("Internal error: missing call parameters")
|
|
1370
|
+
clearPendingCall()
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
private fun clearPendingCall() {
|
|
1375
|
+
pendingCall = null
|
|
1376
|
+
pendingCallUserIds = null
|
|
1377
|
+
pendingCallType = null
|
|
1378
|
+
pendingCallShouldRing = null
|
|
1379
|
+
pendingCallTeam = null
|
|
1380
|
+
pendingAcceptCall = null
|
|
1381
|
+
permissionAttemptCount = 0 // Reset attempt count when clearing
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
@OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
|
|
1385
|
+
private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?) {
|
|
1386
|
+
val selfUserId = streamVideoClient?.userId
|
|
1387
|
+
if (selfUserId == null) {
|
|
1388
|
+
call.reject("No self-user id found. Are you not logged in?")
|
|
1389
|
+
return
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
val callId = java.util.UUID.randomUUID().toString()
|
|
1393
|
+
|
|
1394
|
+
// Create and join call in a coroutine
|
|
1395
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1396
|
+
try {
|
|
1397
|
+
// Create the call object
|
|
1398
|
+
val streamCall = streamVideoClient?.call(type = callType, id = callId)
|
|
1399
|
+
|
|
1400
|
+
// Note: We no longer start tracking here - we'll wait for CallSessionStartedEvent
|
|
1401
|
+
// instead, which contains the actual participant list
|
|
1402
|
+
|
|
1403
|
+
android.util.Log.d("StreamCallPlugin", "Creating call with members...")
|
|
1404
|
+
// Create the call with all members
|
|
1405
|
+
val createResult = streamCall?.create(
|
|
1406
|
+
memberIds = userIds + selfUserId,
|
|
1407
|
+
custom = emptyMap(),
|
|
1408
|
+
ring = shouldRing,
|
|
1409
|
+
team = team,
|
|
1410
|
+
)
|
|
1411
|
+
|
|
1412
|
+
if (createResult?.isFailure == true) {
|
|
1413
|
+
throw (createResult.errorOrNull() ?: RuntimeException("Unknown error creating call")) as Throwable
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
android.util.Log.d("StreamCallPlugin", "Setting overlay visible for outgoing call $callId")
|
|
1417
|
+
// Show overlay view
|
|
1418
|
+
activity?.runOnUiThread {
|
|
1419
|
+
streamCall?.microphone?.setEnabled(true)
|
|
1420
|
+
streamCall?.camera?.setEnabled(true)
|
|
1421
|
+
|
|
1422
|
+
bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
|
|
1423
|
+
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
1424
|
+
setOverlayContent(streamCall)
|
|
1425
|
+
overlayView?.isVisible = true
|
|
1426
|
+
// Ensure overlay is behind WebView by adjusting its position in the parent
|
|
1427
|
+
val parent = overlayView?.parent as? ViewGroup
|
|
1428
|
+
parent?.removeView(overlayView)
|
|
1429
|
+
parent?.addView(overlayView, 0) // Add at index 0 to ensure it's behind other views
|
|
1129
1430
|
}
|
|
1431
|
+
|
|
1432
|
+
// Resolve the call with success
|
|
1433
|
+
call.resolve(JSObject().apply {
|
|
1434
|
+
put("success", true)
|
|
1435
|
+
})
|
|
1436
|
+
} catch (e: Exception) {
|
|
1437
|
+
android.util.Log.e("StreamCallPlugin", "Error making call: ${e.message}")
|
|
1438
|
+
call.reject("Failed to make call: ${e.message}")
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Function to request required permissions
|
|
1444
|
+
private fun requestPermissions() {
|
|
1445
|
+
permissionAttemptCount++
|
|
1446
|
+
android.util.Log.d("StreamCallPlugin", "requestPermissions: Attempt #$permissionAttemptCount - Requesting RECORD_AUDIO and CAMERA permissions.")
|
|
1447
|
+
|
|
1448
|
+
// Record timing for instant denial detection
|
|
1449
|
+
permissionRequestStartTime = System.currentTimeMillis()
|
|
1450
|
+
android.util.Log.d("StreamCallPlugin", "requestPermissions: Starting permission request at $permissionRequestStartTime")
|
|
1451
|
+
|
|
1452
|
+
ActivityCompat.requestPermissions(
|
|
1453
|
+
activity,
|
|
1454
|
+
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA),
|
|
1455
|
+
9001 // Use high request code to avoid Capacitor conflicts
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
android.util.Log.d("StreamCallPlugin", "requestPermissions: Permission request initiated with code 9001")
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
private fun showPermissionSettingsDialog() {
|
|
1462
|
+
activity?.runOnUiThread {
|
|
1463
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1464
|
+
val hasActiveCall = activeCall != null && pendingAcceptCall != null && activeCall.id == pendingAcceptCall?.id
|
|
1465
|
+
|
|
1466
|
+
val builder = AlertDialog.Builder(activity)
|
|
1467
|
+
builder.setTitle("Enable Permissions")
|
|
1468
|
+
|
|
1469
|
+
if (hasActiveCall) {
|
|
1470
|
+
builder.setMessage("Your call is active but camera and microphone are disabled.\n\nWould you like to open Settings to enable video and audio?")
|
|
1471
|
+
builder.setNegativeButton("Continue without") { _, _ ->
|
|
1472
|
+
android.util.Log.d("StreamCallPlugin", "User chose to continue call without permissions")
|
|
1473
|
+
showPermissionRequiredMessage()
|
|
1474
|
+
}
|
|
1475
|
+
} else {
|
|
1476
|
+
builder.setMessage("To make video calls, this app needs Camera and Microphone permissions.\n\nWould you like to open Settings to enable them?")
|
|
1477
|
+
builder.setNegativeButton("Cancel") { _, _ ->
|
|
1478
|
+
android.util.Log.d("StreamCallPlugin", "User declined to grant permissions - final rejection")
|
|
1479
|
+
showPermissionRequiredMessage()
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
builder.setPositiveButton("Open Settings") { _, _ ->
|
|
1484
|
+
android.util.Log.d("StreamCallPlugin", "User chose to open app settings")
|
|
1485
|
+
openAppSettings()
|
|
1486
|
+
// Don't reject the call yet - let them go to settings and come back
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
builder.setCancelable(false)
|
|
1490
|
+
builder.show()
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
private fun showPermissionRequiredMessage() {
|
|
1495
|
+
activity?.runOnUiThread {
|
|
1496
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1497
|
+
val hasActiveCall = activeCall != null && pendingAcceptCall != null && activeCall.id == pendingAcceptCall?.id
|
|
1498
|
+
|
|
1499
|
+
val builder = AlertDialog.Builder(activity)
|
|
1500
|
+
builder.setTitle("Permissions Required")
|
|
1501
|
+
|
|
1502
|
+
if (hasActiveCall) {
|
|
1503
|
+
builder.setMessage("Camera and microphone permissions are required for video calling. Your call will continue without camera/microphone.")
|
|
1130
1504
|
} else {
|
|
1131
|
-
|
|
1505
|
+
builder.setMessage("Camera/microphone permission is required for the calling functionality of this app")
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
builder.setPositiveButton("OK") { dialog, _ ->
|
|
1509
|
+
dialog.dismiss()
|
|
1510
|
+
handleFinalPermissionDenial()
|
|
1511
|
+
}
|
|
1512
|
+
builder.setCancelable(false)
|
|
1513
|
+
builder.show()
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
private fun handleFinalPermissionDenial() {
|
|
1518
|
+
android.util.Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Processing final permission denial")
|
|
1519
|
+
|
|
1520
|
+
val hasOutgoingCall = pendingCall != null && pendingCallUserIds != null
|
|
1521
|
+
val hasIncomingCall = pendingCall != null && pendingAcceptCall != null
|
|
1522
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1523
|
+
|
|
1524
|
+
when {
|
|
1525
|
+
hasOutgoingCall -> {
|
|
1526
|
+
// Outgoing call that couldn't be created due to permissions
|
|
1527
|
+
android.util.Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Rejecting outgoing call creation")
|
|
1528
|
+
pendingCall?.reject("Permissions required for call. Please grant them.")
|
|
1529
|
+
clearPendingCall()
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
hasIncomingCall && activeCall != null && activeCall.id == pendingAcceptCall?.id -> {
|
|
1533
|
+
// Incoming call that's already active - DON'T end the call, just keep it without camera/mic
|
|
1534
|
+
android.util.Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Incoming call already active, keeping call without camera/mic")
|
|
1535
|
+
|
|
1536
|
+
// Ensure camera and microphone are disabled since no permissions
|
|
1537
|
+
try {
|
|
1538
|
+
activeCall.microphone?.setEnabled(false)
|
|
1539
|
+
activeCall.camera?.setEnabled(false)
|
|
1540
|
+
android.util.Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Disabled camera/microphone for call ${activeCall.id}")
|
|
1541
|
+
} catch (e: Exception) {
|
|
1542
|
+
android.util.Log.w("StreamCallPlugin", "handleFinalPermissionDenial: Error disabling camera/mic", e)
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
android.widget.Toast.makeText(
|
|
1546
|
+
context,
|
|
1547
|
+
"Call continues without camera/microphone",
|
|
1548
|
+
android.widget.Toast.LENGTH_LONG
|
|
1549
|
+
).show()
|
|
1550
|
+
|
|
1551
|
+
// Resolve the pending call since the call itself was successful (just no permissions)
|
|
1552
|
+
pendingCall?.resolve(JSObject().apply {
|
|
1553
|
+
put("success", true)
|
|
1554
|
+
put("message", "Call accepted without camera/microphone permissions")
|
|
1555
|
+
})
|
|
1556
|
+
clearPendingCall()
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
hasIncomingCall -> {
|
|
1560
|
+
// Incoming call that wasn't accepted yet (old flow)
|
|
1561
|
+
android.util.Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Rejecting incoming call acceptance")
|
|
1562
|
+
pendingCall?.reject("Permissions required for call. Please grant them.")
|
|
1563
|
+
clearPendingCall()
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
else -> {
|
|
1567
|
+
android.util.Log.d("StreamCallPlugin", "handleFinalPermissionDenial: No pending operations to handle")
|
|
1568
|
+
clearPendingCall()
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
private fun openAppSettings() {
|
|
1574
|
+
try {
|
|
1575
|
+
// Try to open app-specific permission settings directly (Android 11+)
|
|
1576
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
1577
|
+
try {
|
|
1578
|
+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
1579
|
+
("package:" + activity.packageName).toUri())
|
|
1580
|
+
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
|
1581
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
1582
|
+
context.startActivity(intent)
|
|
1583
|
+
android.util.Log.d("StreamCallPlugin", "Opened app details settings (Android 11+)")
|
|
1584
|
+
|
|
1585
|
+
// Show toast with specific instructions
|
|
1586
|
+
runOnMainThread {
|
|
1587
|
+
android.widget.Toast.makeText(
|
|
1588
|
+
context,
|
|
1589
|
+
"Tap 'Permissions' → Enable Camera and Microphone",
|
|
1590
|
+
android.widget.Toast.LENGTH_LONG
|
|
1591
|
+
).show()
|
|
1592
|
+
}
|
|
1593
|
+
return
|
|
1594
|
+
} catch (e: Exception) {
|
|
1595
|
+
android.util.Log.w("StreamCallPlugin", "Failed to open app details, falling back", e)
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Fallback for older Android versions or if the above fails
|
|
1600
|
+
val intent = android.content.Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
1601
|
+
data = Uri.fromParts("package", context.packageName, null)
|
|
1602
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
1603
|
+
}
|
|
1604
|
+
context.startActivity(intent)
|
|
1605
|
+
android.util.Log.d("StreamCallPlugin", "Opened app settings via fallback")
|
|
1606
|
+
|
|
1607
|
+
// Show more specific instructions for older versions
|
|
1608
|
+
runOnMainThread {
|
|
1609
|
+
android.widget.Toast.makeText(
|
|
1610
|
+
context,
|
|
1611
|
+
"Find 'Permissions' and enable Camera + Microphone",
|
|
1612
|
+
android.widget.Toast.LENGTH_LONG
|
|
1613
|
+
).show()
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
} catch (e: Exception) {
|
|
1617
|
+
android.util.Log.e("StreamCallPlugin", "Error opening app settings", e)
|
|
1618
|
+
|
|
1619
|
+
// Final fallback - open general settings
|
|
1620
|
+
try {
|
|
1621
|
+
val intent = android.content.Intent(android.provider.Settings.ACTION_SETTINGS).apply {
|
|
1622
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
1623
|
+
}
|
|
1624
|
+
context.startActivity(intent)
|
|
1625
|
+
android.util.Log.d("StreamCallPlugin", "Opened general settings as final fallback")
|
|
1626
|
+
|
|
1132
1627
|
runOnMainThread {
|
|
1133
1628
|
android.widget.Toast.makeText(
|
|
1134
1629
|
context,
|
|
1135
|
-
"
|
|
1630
|
+
"Go to Apps → ${context.applicationInfo.loadLabel(context.packageManager)} → Permissions",
|
|
1631
|
+
android.widget.Toast.LENGTH_LONG
|
|
1632
|
+
).show()
|
|
1633
|
+
}
|
|
1634
|
+
} catch (finalException: Exception) {
|
|
1635
|
+
android.util.Log.e("StreamCallPlugin", "All settings intents failed", finalException)
|
|
1636
|
+
runOnMainThread {
|
|
1637
|
+
android.widget.Toast.makeText(
|
|
1638
|
+
context,
|
|
1639
|
+
"Please manually enable Camera and Microphone permissions",
|
|
1136
1640
|
android.widget.Toast.LENGTH_LONG
|
|
1137
1641
|
).show()
|
|
1138
1642
|
}
|
|
1139
1643
|
}
|
|
1140
|
-
} else {
|
|
1141
|
-
android.util.Log.w("StreamCallPlugin", "handleRequestPermissionsResult: Received unknown requestCode: $requestCode")
|
|
1142
1644
|
}
|
|
1143
1645
|
}
|
|
1144
1646
|
|
|
1145
|
-
|
|
1146
|
-
android.util.Log.d("StreamCallPlugin", "logPermissionResults: Logging permission results:")
|
|
1147
|
-
for (i in permissions.indices) {
|
|
1148
|
-
val permission = permissions[i]
|
|
1149
|
-
val grantResult = if (grantResults.size > i) grantResults[i] else -999 // -999 for safety if arrays mismatch
|
|
1150
|
-
val resultString = if (grantResult == PackageManager.PERMISSION_GRANTED) "GRANTED" else "DENIED ($grantResult)"
|
|
1151
|
-
android.util.Log.d("StreamCallPlugin", " Permission: $permission, Result: $resultString")
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1647
|
+
|
|
1154
1648
|
|
|
1155
1649
|
@OptIn(DelicateCoroutinesApi::class)
|
|
1156
1650
|
@PluginMethod
|
|
@@ -1455,58 +1949,21 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1455
1949
|
|
|
1456
1950
|
// Check permissions before creating the call
|
|
1457
1951
|
if (!checkPermissions()) {
|
|
1952
|
+
android.util.Log.d("StreamCallPlugin", "Permissions not granted, storing call parameters and requesting permissions")
|
|
1953
|
+
// Store call parameters for later execution
|
|
1954
|
+
pendingCall = call
|
|
1955
|
+
pendingCallUserIds = userIds
|
|
1956
|
+
pendingCallType = callType
|
|
1957
|
+
pendingCallShouldRing = shouldRing
|
|
1958
|
+
pendingCallTeam = team
|
|
1959
|
+
// Reset attempt count for new permission flow
|
|
1960
|
+
permissionAttemptCount = 0
|
|
1458
1961
|
requestPermissions()
|
|
1459
|
-
|
|
1460
|
-
return
|
|
1962
|
+
return // Don't reject immediately, wait for permission result
|
|
1461
1963
|
}
|
|
1462
1964
|
|
|
1463
|
-
//
|
|
1464
|
-
|
|
1465
|
-
try {
|
|
1466
|
-
// Create the call object
|
|
1467
|
-
val streamCall = streamVideoClient?.call(type = callType, id = callId)
|
|
1468
|
-
|
|
1469
|
-
// Note: We no longer start tracking here - we'll wait for CallSessionStartedEvent
|
|
1470
|
-
// instead, which contains the actual participant list
|
|
1471
|
-
|
|
1472
|
-
android.util.Log.d("StreamCallPlugin", "Creating call with members...")
|
|
1473
|
-
// Create the call with all members
|
|
1474
|
-
val createResult = streamCall?.create(
|
|
1475
|
-
memberIds = userIds + selfUserId,
|
|
1476
|
-
custom = emptyMap(),
|
|
1477
|
-
ring = shouldRing,
|
|
1478
|
-
team = team,
|
|
1479
|
-
)
|
|
1480
|
-
|
|
1481
|
-
if (createResult?.isFailure == true) {
|
|
1482
|
-
throw (createResult.errorOrNull() ?: RuntimeException("Unknown error creating call")) as Throwable
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
android.util.Log.d("StreamCallPlugin", "Setting overlay visible for outgoing call $callId")
|
|
1486
|
-
// Show overlay view
|
|
1487
|
-
activity?.runOnUiThread {
|
|
1488
|
-
streamCall?.microphone?.setEnabled(true)
|
|
1489
|
-
streamCall?.camera?.setEnabled(true)
|
|
1490
|
-
|
|
1491
|
-
bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
|
|
1492
|
-
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
1493
|
-
setOverlayContent(streamCall)
|
|
1494
|
-
overlayView?.isVisible = true
|
|
1495
|
-
// Ensure overlay is behind WebView by adjusting its position in the parent
|
|
1496
|
-
val parent = overlayView?.parent as? ViewGroup
|
|
1497
|
-
parent?.removeView(overlayView)
|
|
1498
|
-
parent?.addView(overlayView, 0) // Add at index 0 to ensure it's behind other views
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
// Resolve the call with success
|
|
1502
|
-
call.resolve(JSObject().apply {
|
|
1503
|
-
put("success", true)
|
|
1504
|
-
})
|
|
1505
|
-
} catch (e: Exception) {
|
|
1506
|
-
android.util.Log.e("StreamCallPlugin", "Error making call: ${e.message}")
|
|
1507
|
-
call.reject("Failed to make call: ${e.message}")
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1965
|
+
// Execute call creation immediately if permissions are granted
|
|
1966
|
+
createAndStartCall(call, userIds, callType, shouldRing, team)
|
|
1510
1967
|
} catch (e: Exception) {
|
|
1511
1968
|
call.reject("Failed to make call: ${e.message}")
|
|
1512
1969
|
}
|
|
@@ -1722,7 +2179,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1722
2179
|
call.reject("No active call")
|
|
1723
2180
|
}
|
|
1724
2181
|
}
|
|
1725
|
-
|
|
2182
|
+
|
|
1726
2183
|
// Helper method to update call status and notify listeners
|
|
1727
2184
|
private fun updateCallStatusAndNotify(callId: String, state: String, userId: String? = null, reason: String? = null, members: List<Map<String, Any>>? = null, caller: Map<String, Any>? = null) {
|
|
1728
2185
|
android.util.Log.d("StreamCallPlugin", "updateCallStatusAndNotify called: callId=$callId, state=$state, userId=$userId, reason=$reason")
|
|
@@ -1820,7 +2277,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1820
2277
|
val call = streamVideoClient?.call(id = cid.id, type = cid.type)
|
|
1821
2278
|
if (call != null) {
|
|
1822
2279
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1823
|
-
internalAcceptCall(call)
|
|
2280
|
+
internalAcceptCall(call, requestPermissionsAfter = !checkPermissions())
|
|
1824
2281
|
}
|
|
1825
2282
|
bringAppToForeground()
|
|
1826
2283
|
} else {
|
package/dist/docs.json
CHANGED
|
@@ -1565,6 +1565,22 @@
|
|
|
1565
1565
|
"text": "'ended'",
|
|
1566
1566
|
"complexTypes": []
|
|
1567
1567
|
},
|
|
1568
|
+
{
|
|
1569
|
+
"text": "'camera_enabled'",
|
|
1570
|
+
"complexTypes": []
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
"text": "'camera_disabled'",
|
|
1574
|
+
"complexTypes": []
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
"text": "'microphone_enabled'",
|
|
1578
|
+
"complexTypes": []
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
"text": "'microphone_disabled'",
|
|
1582
|
+
"complexTypes": []
|
|
1583
|
+
},
|
|
1568
1584
|
{
|
|
1569
1585
|
"text": "'unknown'",
|
|
1570
1586
|
"complexTypes": []
|
|
@@ -31,7 +31,7 @@ export interface PushNotificationsConfig {
|
|
|
31
31
|
* @typedef CallState
|
|
32
32
|
* @description Represents all possible call states from API and UI
|
|
33
33
|
*/
|
|
34
|
-
export type CallState = 'idle' | 'ringing' | 'joining' | 'reconnecting' | 'joined' | 'leaving' | 'left' | 'created' | 'session_started' | 'rejected' | 'missed' | 'accepted' | 'ended' | 'unknown';
|
|
34
|
+
export type CallState = 'idle' | 'ringing' | 'joining' | 'reconnecting' | 'joined' | 'leaving' | 'left' | 'created' | 'session_started' | 'rejected' | 'missed' | 'accepted' | 'ended' | 'camera_enabled' | 'camera_disabled' | 'microphone_enabled' | 'microphone_disabled' | 'unknown';
|
|
35
35
|
/**
|
|
36
36
|
* @typedef CallType
|
|
37
37
|
* @description Represents the pre-defined types of a call.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @interface LoginOptions\n * @description Configuration options for logging into the Stream Video service\n * @property {string} token - Stream Video API token for authentication\n * @property {string} userId - Unique identifier for the current user\n * @property {string} name - Display name for the current user\n * @property {string} [imageURL] - Avatar URL for the current user\n * @property {string} apiKey - Stream Video API key for your application\n * @property {string} [magicDivId] - DOM element ID where video will be rendered\n */\nexport interface LoginOptions {\n /** Stream Video API token */\n token: string;\n /** User ID for the current user */\n userId: string;\n /** Display name for the current user */\n name: string;\n /** Optional avatar URL for the current user */\n imageURL?: string;\n /** Stream Video API key */\n apiKey: string;\n /** ID of the HTML element where the video will be rendered */\n magicDivId?: string;\n pushNotificationsConfig?: PushNotificationsConfig;\n}\n\nexport interface PushNotificationsConfig {\n pushProviderName: string;\n voipProviderName: string;\n}\n\n/**\n * @typedef CallState\n * @description Represents all possible call states from API and UI\n */\nexport type CallState =\n // User-facing states\n | 'idle'\n | 'ringing'\n | 'joining'\n | 'reconnecting'\n | 'joined'\n | 'leaving'\n | 'left'\n // Event-specific states\n | 'created'\n | 'session_started'\n | 'rejected'\n | 'missed'\n | 'accepted'\n | 'ended'\n | 'unknown';\n\n/**\n * @typedef CallType\n * @description Represents the pre-defined types of a call.\n * - `default`: Simple 1-1 or group video calling with sensible defaults. Video/audio enabled, backstage disabled. Admins/hosts have elevated permissions.\n * - `audio_room`: For audio-only spaces (like Clubhouse). Backstage enabled (requires `goLive`), pre-configured permissions for requesting to speak.\n * - `livestream`: For one-to-many streaming. Backstage enabled (requires `goLive`), access granted to all authenticated users.\n * - `development`: For testing ONLY. All permissions enabled, backstage disabled. **Not recommended for production.**\n */\nexport type CallType = 'default' | 'audio_room' | 'livestream' | 'development';\n\n/**\n * @interface CallMember\n * @description Information about a call member/participant\n * @property {string} userId - User ID of the member\n * @property {string} [name] - Display name of the user\n * @property {string} [imageURL] - Profile image URL of the user\n * @property {string} [role] - Role of the user in the call\n */\nexport interface CallMember {\n /** User ID of the member */\n userId: string;\n /** Display name of the user */\n name?: string;\n /** Profile image URL of the user */\n imageURL?: string;\n /** Role of the user in the call */\n role?: string;\n}\n\n/**\n * @interface CallEvent\n * @description Event emitted when call state changes\n * @property {string} callId - Unique identifier of the call\n * @property {CallState} state - Current state of the call\n * @property {string} [userId] - User ID of the participant who triggered the event\n * @property {string} [reason] - Reason for the call state change\n * @property {CallMember} [caller] - Information about the caller (for incoming calls)\n * @property {CallMember[]} [members] - List of call members\n */\nexport interface CallEvent {\n /** ID of the call */\n callId: string;\n /** Current state of the call */\n state: CallState;\n /** User ID of the participant in the call who triggered the event */\n userId?: string;\n /** Reason for the call state change, if applicable */\n reason?: string;\n /** Information about the caller (for incoming calls) */\n caller?: CallMember;\n /** List of call members */\n members?: CallMember[];\n}\n\nexport interface CameraEnabledResponse {\n enabled: boolean;\n}\n\n/**\n * @interface SuccessResponse\n * @description Standard response indicating operation success/failure\n * @property {boolean} success - Whether the operation succeeded\n */\nexport interface SuccessResponse {\n /** Whether the operation was successful */\n success: boolean;\n}\n\n/**\n * @interface CallOptions\n * @description Options for initiating a video call\n * @property {string[]} userIds - IDs of the users to call\n * @property {CallType} [type=default] - Type of call\n * @property {boolean} [ring=true] - Whether to send ring notification\n * @property {string} [team] - Team name to call\n */\nexport interface CallOptions {\n /** User ID of the person to call */\n userIds: string[];\n /** Type of call, defaults to 'default' */\n type?: CallType;\n /** Whether to ring the other user, defaults to true */\n ring?: boolean;\n /** Team name to call */\n team?: string;\n /** Whether to start the call with video enabled, defaults to false */\n video?: boolean;\n}\n\n/**\n * @interface StreamCallPlugin\n * @description Capacitor plugin for Stream Video calling functionality\n */\nexport interface StreamCallPlugin {\n /**\n * Login to Stream Video service\n * @param {LoginOptions} options - Login configuration\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.login({\n * token: 'your-token',\n * userId: 'user-123',\n * name: 'John Doe',\n * apiKey: 'your-api-key'\n * });\n */\n login(options: LoginOptions): Promise<SuccessResponse>;\n\n /**\n * Logout from Stream Video service\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.logout();\n */\n logout(): Promise<SuccessResponse>;\n\n /**\n * Initiate a call to another user\n * @param {CallOptions} options - Call configuration\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.call({\n * userId: 'user-456',\n * type: 'video',\n * ring: true\n * });\n */\n call(options: CallOptions): Promise<SuccessResponse>;\n\n /**\n * End the current call\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.endCall();\n */\n endCall(): Promise<SuccessResponse>;\n\n /**\n * Enable or disable microphone\n * @param {{ enabled: boolean }} options - Microphone state\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.setMicrophoneEnabled({ enabled: false });\n */\n setMicrophoneEnabled(options: { enabled: boolean }): Promise<SuccessResponse>;\n\n /**\n * Enable or disable camera\n * @param {{ enabled: boolean }} options - Camera state\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.setCameraEnabled({ enabled: false });\n */\n setCameraEnabled(options: { enabled: boolean }): Promise<SuccessResponse>;\n\n /**\n * Add listener for call events\n * @param {'callEvent'} eventName - Name of the event to listen for\n * @param {(event: CallEvent) => void} listenerFunc - Callback function\n * @returns {Promise<{ remove: () => Promise<void> }>} Function to remove listener\n * @example\n * const listener = await StreamCall.addListener('callEvent', (event) => {\n * console.log(`Call ${event.callId} is now ${event.state}`);\n * });\n */\n addListener(\n eventName: 'callEvent',\n listenerFunc: (event: CallEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Listen for lock-screen incoming call (Android only).\n * Fired when the app is shown by full-screen intent before user interaction.\n */\n addListener(\n eventName: 'incomingCall',\n listenerFunc: (event: IncomingCallPayload) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Remove all event listeners\n * @returns {Promise<void>}\n * @example\n * await StreamCall.removeAllListeners();\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Accept an incoming call\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.acceptCall();\n */\n acceptCall(): Promise<SuccessResponse>;\n\n /**\n * Reject an incoming call\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.rejectCall();\n */\n rejectCall(): Promise<SuccessResponse>;\n\n /**\n * Check if camera is enabled\n * @returns {Promise<CameraEnabledResponse>} Camera enabled status\n * @example\n * const isCameraEnabled = await StreamCall.isCameraEnabled();\n * console.log(isCameraEnabled);\n */\n isCameraEnabled(): Promise<CameraEnabledResponse>;\n\n /**\n * Get the current call status\n * @returns {Promise<CallEvent>} Current call status as a CallEvent\n * @example\n * const callStatus = await StreamCall.getCallStatus();\n * console.log(callStatus);\n */\n getCallStatus(): Promise<CallEvent>;\n\n /**\n * Set speakerphone on\n * @param {{ name: string }} options - Speakerphone name\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.setSpeaker({ name: 'speaker' });\n */\n setSpeaker(options: { name: string }): Promise<SuccessResponse>;\n\n /**\n * Switch camera\n * @param {{ camera: 'front' | 'back' }} options - Camera to switch to\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.switchCamera({ camera: 'back' });\n */\n switchCamera(options: { camera: 'front' | 'back' }): Promise<SuccessResponse>;\n\n /**\n * Get detailed information about an active call including caller details\n * @param options - Options containing the call ID\n */\n getCallInfo(options: { callId: string }): Promise<CallEvent>;\n}\n\n/**\n * @interface IncomingCallPayload\n * @description Payload delivered with \"incomingCall\" event (Android lock-screen).\n * @property {string} cid - Call CID (type:id)\n * @property {string} type - Always \"incoming\" for this event\n * @property {CallMember} [caller] - Information about the caller\n */\nexport interface IncomingCallPayload {\n /** Full call CID (e.g. default:123) */\n cid: string;\n /** Event type (currently always \"incoming\") */\n type: 'incoming';\n /** Information about the caller */\n caller?: CallMember;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @interface LoginOptions\n * @description Configuration options for logging into the Stream Video service\n * @property {string} token - Stream Video API token for authentication\n * @property {string} userId - Unique identifier for the current user\n * @property {string} name - Display name for the current user\n * @property {string} [imageURL] - Avatar URL for the current user\n * @property {string} apiKey - Stream Video API key for your application\n * @property {string} [magicDivId] - DOM element ID where video will be rendered\n */\nexport interface LoginOptions {\n /** Stream Video API token */\n token: string;\n /** User ID for the current user */\n userId: string;\n /** Display name for the current user */\n name: string;\n /** Optional avatar URL for the current user */\n imageURL?: string;\n /** Stream Video API key */\n apiKey: string;\n /** ID of the HTML element where the video will be rendered */\n magicDivId?: string;\n pushNotificationsConfig?: PushNotificationsConfig;\n}\n\nexport interface PushNotificationsConfig {\n pushProviderName: string;\n voipProviderName: string;\n}\n\n/**\n * @typedef CallState\n * @description Represents all possible call states from API and UI\n */\nexport type CallState =\n // User-facing states\n | 'idle'\n | 'ringing'\n | 'joining'\n | 'reconnecting'\n | 'joined'\n | 'leaving'\n | 'left'\n // Event-specific states\n | 'created'\n | 'session_started'\n | 'rejected'\n | 'missed'\n | 'accepted'\n | 'ended'\n | 'camera_enabled'\n | 'camera_disabled'\n | 'microphone_enabled'\n | 'microphone_disabled'\n | 'unknown';\n\n/**\n * @typedef CallType\n * @description Represents the pre-defined types of a call.\n * - `default`: Simple 1-1 or group video calling with sensible defaults. Video/audio enabled, backstage disabled. Admins/hosts have elevated permissions.\n * - `audio_room`: For audio-only spaces (like Clubhouse). Backstage enabled (requires `goLive`), pre-configured permissions for requesting to speak.\n * - `livestream`: For one-to-many streaming. Backstage enabled (requires `goLive`), access granted to all authenticated users.\n * - `development`: For testing ONLY. All permissions enabled, backstage disabled. **Not recommended for production.**\n */\nexport type CallType = 'default' | 'audio_room' | 'livestream' | 'development';\n\n/**\n * @interface CallMember\n * @description Information about a call member/participant\n * @property {string} userId - User ID of the member\n * @property {string} [name] - Display name of the user\n * @property {string} [imageURL] - Profile image URL of the user\n * @property {string} [role] - Role of the user in the call\n */\nexport interface CallMember {\n /** User ID of the member */\n userId: string;\n /** Display name of the user */\n name?: string;\n /** Profile image URL of the user */\n imageURL?: string;\n /** Role of the user in the call */\n role?: string;\n}\n\n/**\n * @interface CallEvent\n * @description Event emitted when call state changes\n * @property {string} callId - Unique identifier of the call\n * @property {CallState} state - Current state of the call\n * @property {string} [userId] - User ID of the participant who triggered the event\n * @property {string} [reason] - Reason for the call state change\n * @property {CallMember} [caller] - Information about the caller (for incoming calls)\n * @property {CallMember[]} [members] - List of call members\n */\nexport interface CallEvent {\n /** ID of the call */\n callId: string;\n /** Current state of the call */\n state: CallState;\n /** User ID of the participant in the call who triggered the event */\n userId?: string;\n /** Reason for the call state change, if applicable */\n reason?: string;\n /** Information about the caller (for incoming calls) */\n caller?: CallMember;\n /** List of call members */\n members?: CallMember[];\n}\n\nexport interface CameraEnabledResponse {\n enabled: boolean;\n}\n\n/**\n * @interface SuccessResponse\n * @description Standard response indicating operation success/failure\n * @property {boolean} success - Whether the operation succeeded\n */\nexport interface SuccessResponse {\n /** Whether the operation was successful */\n success: boolean;\n}\n\n/**\n * @interface CallOptions\n * @description Options for initiating a video call\n * @property {string[]} userIds - IDs of the users to call\n * @property {CallType} [type=default] - Type of call\n * @property {boolean} [ring=true] - Whether to send ring notification\n * @property {string} [team] - Team name to call\n */\nexport interface CallOptions {\n /** User ID of the person to call */\n userIds: string[];\n /** Type of call, defaults to 'default' */\n type?: CallType;\n /** Whether to ring the other user, defaults to true */\n ring?: boolean;\n /** Team name to call */\n team?: string;\n /** Whether to start the call with video enabled, defaults to false */\n video?: boolean;\n}\n\n/**\n * @interface StreamCallPlugin\n * @description Capacitor plugin for Stream Video calling functionality\n */\nexport interface StreamCallPlugin {\n /**\n * Login to Stream Video service\n * @param {LoginOptions} options - Login configuration\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.login({\n * token: 'your-token',\n * userId: 'user-123',\n * name: 'John Doe',\n * apiKey: 'your-api-key'\n * });\n */\n login(options: LoginOptions): Promise<SuccessResponse>;\n\n /**\n * Logout from Stream Video service\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.logout();\n */\n logout(): Promise<SuccessResponse>;\n\n /**\n * Initiate a call to another user\n * @param {CallOptions} options - Call configuration\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.call({\n * userId: 'user-456',\n * type: 'video',\n * ring: true\n * });\n */\n call(options: CallOptions): Promise<SuccessResponse>;\n\n /**\n * End the current call\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.endCall();\n */\n endCall(): Promise<SuccessResponse>;\n\n /**\n * Enable or disable microphone\n * @param {{ enabled: boolean }} options - Microphone state\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.setMicrophoneEnabled({ enabled: false });\n */\n setMicrophoneEnabled(options: { enabled: boolean }): Promise<SuccessResponse>;\n\n /**\n * Enable or disable camera\n * @param {{ enabled: boolean }} options - Camera state\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.setCameraEnabled({ enabled: false });\n */\n setCameraEnabled(options: { enabled: boolean }): Promise<SuccessResponse>;\n\n /**\n * Add listener for call events\n * @param {'callEvent'} eventName - Name of the event to listen for\n * @param {(event: CallEvent) => void} listenerFunc - Callback function\n * @returns {Promise<{ remove: () => Promise<void> }>} Function to remove listener\n * @example\n * const listener = await StreamCall.addListener('callEvent', (event) => {\n * console.log(`Call ${event.callId} is now ${event.state}`);\n * });\n */\n addListener(\n eventName: 'callEvent',\n listenerFunc: (event: CallEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Listen for lock-screen incoming call (Android only).\n * Fired when the app is shown by full-screen intent before user interaction.\n */\n addListener(\n eventName: 'incomingCall',\n listenerFunc: (event: IncomingCallPayload) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Remove all event listeners\n * @returns {Promise<void>}\n * @example\n * await StreamCall.removeAllListeners();\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Accept an incoming call\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.acceptCall();\n */\n acceptCall(): Promise<SuccessResponse>;\n\n /**\n * Reject an incoming call\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.rejectCall();\n */\n rejectCall(): Promise<SuccessResponse>;\n\n /**\n * Check if camera is enabled\n * @returns {Promise<CameraEnabledResponse>} Camera enabled status\n * @example\n * const isCameraEnabled = await StreamCall.isCameraEnabled();\n * console.log(isCameraEnabled);\n */\n isCameraEnabled(): Promise<CameraEnabledResponse>;\n\n /**\n * Get the current call status\n * @returns {Promise<CallEvent>} Current call status as a CallEvent\n * @example\n * const callStatus = await StreamCall.getCallStatus();\n * console.log(callStatus);\n */\n getCallStatus(): Promise<CallEvent>;\n\n /**\n * Set speakerphone on\n * @param {{ name: string }} options - Speakerphone name\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.setSpeaker({ name: 'speaker' });\n */\n setSpeaker(options: { name: string }): Promise<SuccessResponse>;\n\n /**\n * Switch camera\n * @param {{ camera: 'front' | 'back' }} options - Camera to switch to\n * @returns {Promise<SuccessResponse>} Success status\n * @example\n * await StreamCall.switchCamera({ camera: 'back' });\n */\n switchCamera(options: { camera: 'front' | 'back' }): Promise<SuccessResponse>;\n\n /**\n * Get detailed information about an active call including caller details\n * @param options - Options containing the call ID\n */\n getCallInfo(options: { callId: string }): Promise<CallEvent>;\n}\n\n/**\n * @interface IncomingCallPayload\n * @description Payload delivered with \"incomingCall\" event (Android lock-screen).\n * @property {string} cid - Call CID (type:id)\n * @property {string} type - Always \"incoming\" for this event\n * @property {CallMember} [caller] - Information about the caller\n */\nexport interface IncomingCallPayload {\n /** Full call CID (e.g. default:123) */\n cid: string;\n /** Event type (currently always \"incoming\") */\n type: 'incoming';\n /** Information about the caller */\n caller?: CallMember;\n}\n"]}
|
package/package.json
CHANGED