@capgo/capacitor-stream-call 0.0.83 → 0.0.85
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/CapgoCapacitorStreamCall.podspec +2 -2
- package/Package.swift +1 -1
- package/README.md +2 -2
- package/android/build.gradle +2 -2
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +164 -66
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +1 -1
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
|
|
|
13
13
|
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
14
|
s.ios.deployment_target = '14.0'
|
|
15
15
|
s.dependency 'Capacitor'
|
|
16
|
-
s.dependency 'StreamVideo', '1.
|
|
17
|
-
s.dependency 'StreamVideoSwiftUI', '1.
|
|
16
|
+
s.dependency 'StreamVideo', '1.28.0'
|
|
17
|
+
s.dependency 'StreamVideoSwiftUI', '1.28.0'
|
|
18
18
|
s.swift_version = '5.1'
|
|
19
19
|
end
|
package/Package.swift
CHANGED
|
@@ -11,7 +11,7 @@ let package = Package(
|
|
|
11
11
|
],
|
|
12
12
|
dependencies: [
|
|
13
13
|
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
|
|
14
|
-
.package(url: "https://github.com/GetStream/stream-video-swift.git", exact: "1.
|
|
14
|
+
.package(url: "https://github.com/GetStream/stream-video-swift.git", exact: "1.28.0")
|
|
15
15
|
],
|
|
16
16
|
targets: [
|
|
17
17
|
.target(
|
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
|
|
3
3
|
|
|
4
4
|
<div align="center">
|
|
5
|
-
<h2><a href="https://capgo.app/?ref=plugin"> ➡️ Get Instant updates for your App with Capgo
|
|
6
|
-
<h2><a href="https://capgo.app/consulting/?ref=plugin">
|
|
5
|
+
<h2><a href="https://capgo.app/?ref=plugin"> ➡️ Get Instant updates for your App with Capgo</a></h2>
|
|
6
|
+
<h2><a href="https://capgo.app/consulting/?ref=plugin"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
A Capacitor plugin that uses the [Stream Video SDK](https://getstream.io/) to enable video calling functionality in your app.
|
package/android/build.gradle
CHANGED
|
@@ -75,8 +75,8 @@ dependencies {
|
|
|
75
75
|
implementation "androidx.compose.material3:material3:1.3.2"
|
|
76
76
|
|
|
77
77
|
// Stream dependencies
|
|
78
|
-
implementation("io.getstream:stream-video-android-ui-compose:1.9.
|
|
79
|
-
implementation("io.getstream:stream-video-android-core:1.9.
|
|
78
|
+
implementation("io.getstream:stream-video-android-ui-compose:1.9.2")
|
|
79
|
+
implementation("io.getstream:stream-video-android-core:1.9.2")
|
|
80
80
|
implementation("io.getstream:stream-android-push:1.3.2")
|
|
81
81
|
implementation("io.getstream:stream-android-push-firebase:1.3.2")
|
|
82
82
|
|
|
@@ -110,6 +110,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
110
110
|
private var cameraStatusJob: Job? = null
|
|
111
111
|
private var microphoneStatusJob: Job? = null
|
|
112
112
|
private var lastEventSent: String? = null
|
|
113
|
+
private var callIsAudioOnly: Boolean = false
|
|
113
114
|
|
|
114
115
|
// Store current call info
|
|
115
116
|
private var currentCallId: String = ""
|
|
@@ -132,6 +133,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
132
133
|
private var pendingCallTeam: String? = null
|
|
133
134
|
private var pendingCustomObject: JSObject? = null
|
|
134
135
|
private var pendingAcceptCall: Call? = null // Store the actual call object for acceptance
|
|
136
|
+
private var pendingSetCameraCall: PluginCall? = null
|
|
135
137
|
|
|
136
138
|
private enum class State {
|
|
137
139
|
NOT_INITIALIZED,
|
|
@@ -162,7 +164,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
162
164
|
Log.d("StreamCallPlugin", "handleOnResume: Permission attempt count: $permissionAttemptCount")
|
|
163
165
|
|
|
164
166
|
// Check if permissions were granted after returning from settings or permission dialog
|
|
165
|
-
if (checkPermissions()) {
|
|
167
|
+
if (checkPermissions(this.callIsAudioOnly)) {
|
|
166
168
|
Log.d("StreamCallPlugin", "handleOnResume: Permissions are now granted")
|
|
167
169
|
// Handle any pending calls that were waiting for permissions
|
|
168
170
|
handlePermissionGranted()
|
|
@@ -181,11 +183,11 @@ class StreamCallPlugin : Plugin() {
|
|
|
181
183
|
if (pendingAcceptCall != null) {
|
|
182
184
|
Log.d("StreamCallPlugin", "handleOnResume: Have active call waiting for permissions, requesting now")
|
|
183
185
|
permissionAttemptCount = 0
|
|
184
|
-
requestPermissions()
|
|
186
|
+
requestPermissions(this.callIsAudioOnly)
|
|
185
187
|
} else if (pendingCall != null && pendingCallUserIds != null) {
|
|
186
188
|
Log.d("StreamCallPlugin", "handleOnResume: Have outgoing call waiting for permissions, requesting now")
|
|
187
189
|
permissionAttemptCount = 0
|
|
188
|
-
requestPermissions()
|
|
190
|
+
requestPermissions(this.callIsAudioOnly)
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
193
|
} else {
|
|
@@ -194,11 +196,20 @@ class StreamCallPlugin : Plugin() {
|
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
override fun load() {
|
|
199
|
+
try {
|
|
200
|
+
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
201
|
+
if (packageInfo.firstInstallTime == packageInfo.lastUpdateTime) {
|
|
202
|
+
Log.d("StreamCallPlugin", "Fresh install detected, clearing user credentials.")
|
|
203
|
+
SecureUserRepository.getInstance(context).removeCurrentUser()
|
|
204
|
+
}
|
|
205
|
+
} catch (e: Exception) {
|
|
206
|
+
Log.e("StreamCallPlugin", "Error checking for fresh install", e)
|
|
207
|
+
}
|
|
197
208
|
// general init
|
|
198
209
|
initializeStreamVideo()
|
|
199
210
|
setupViews()
|
|
200
211
|
super.load()
|
|
201
|
-
checkPermissions()
|
|
212
|
+
checkPermissions(this.callIsAudioOnly)
|
|
202
213
|
// Register broadcast receiver for ACCEPT_CALL action with high priority
|
|
203
214
|
val filter = IntentFilter("io.getstream.video.android.action.ACCEPT_CALL")
|
|
204
215
|
filter.priority = 999 // Set high priority to ensure it captures the intent
|
|
@@ -296,7 +307,9 @@ class StreamCallPlugin : Plugin() {
|
|
|
296
307
|
Log.d("StreamCallPlugin", " [$index] ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
|
|
297
308
|
}
|
|
298
309
|
kotlinx.coroutines.GlobalScope.launch {
|
|
299
|
-
|
|
310
|
+
val isAudioOnly = getIsAudioOnly(call)
|
|
311
|
+
this@StreamCallPlugin.callIsAudioOnly = isAudioOnly
|
|
312
|
+
internalAcceptCall(call, requestPermissionsAfter = !checkPermissions(isAudioOnly))
|
|
300
313
|
}
|
|
301
314
|
bringAppToForeground()
|
|
302
315
|
} else {
|
|
@@ -1016,31 +1029,21 @@ class StreamCallPlugin : Plugin() {
|
|
|
1016
1029
|
|
|
1017
1030
|
@PluginMethod
|
|
1018
1031
|
fun acceptCall(call: PluginCall) {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
val streamVideoCall = streamVideoClient?.state?.ringingCall?.value
|
|
1022
|
-
if (streamVideoCall == null) {
|
|
1023
|
-
call.reject("Ringing call is null")
|
|
1024
|
-
return
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
Log.d("StreamCallPlugin", "acceptCall: Accepting call immediately, will handle permissions after")
|
|
1028
|
-
|
|
1029
|
-
// Accept call immediately regardless of permissions - time is critical!
|
|
1032
|
+
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1033
|
+
if (ringingCall != null) {
|
|
1030
1034
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1031
1035
|
try {
|
|
1032
|
-
|
|
1036
|
+
val isAudioOnly = getIsAudioOnly(ringingCall)
|
|
1037
|
+
internalAcceptCall(ringingCall, requestPermissionsAfter = !checkPermissions(isAudioOnly))
|
|
1033
1038
|
call.resolve(JSObject().apply {
|
|
1034
1039
|
put("success", true)
|
|
1035
1040
|
})
|
|
1036
1041
|
} catch (e: Exception) {
|
|
1037
|
-
Log.e("StreamCallPlugin", "Error accepting call", e)
|
|
1038
1042
|
call.reject("Failed to accept call: ${e.message}")
|
|
1039
1043
|
}
|
|
1040
1044
|
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
call.reject("Cannot acceptCall")
|
|
1045
|
+
} else {
|
|
1046
|
+
call.reject("No ringing call")
|
|
1044
1047
|
}
|
|
1045
1048
|
}
|
|
1046
1049
|
|
|
@@ -1068,6 +1071,9 @@ class StreamCallPlugin : Plugin() {
|
|
|
1068
1071
|
|
|
1069
1072
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1070
1073
|
try {
|
|
1074
|
+
val isAudioOnly = getIsAudioOnly(call)
|
|
1075
|
+
this@StreamCallPlugin.callIsAudioOnly = isAudioOnly
|
|
1076
|
+
|
|
1071
1077
|
Log.d("StreamCallPlugin", "internalAcceptCall: Coroutine started for call ${call.id}")
|
|
1072
1078
|
|
|
1073
1079
|
// Hide incoming call view first
|
|
@@ -1097,15 +1103,15 @@ class StreamCallPlugin : Plugin() {
|
|
|
1097
1103
|
Log.d("StreamCallPlugin", "internalAcceptCall: WebView background set to transparent for call ${call.id}")
|
|
1098
1104
|
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
1099
1105
|
Log.d("StreamCallPlugin", "internalAcceptCall: WebView brought to front for call ${call.id}")
|
|
1100
|
-
|
|
1106
|
+
|
|
1101
1107
|
// Enable camera/microphone based on permissions
|
|
1102
|
-
val hasPermissions = checkPermissions()
|
|
1108
|
+
val hasPermissions = checkPermissions(isAudioOnly)
|
|
1103
1109
|
Log.d("StreamCallPlugin", "internalAcceptCall: Has permissions: $hasPermissions for call ${call.id}")
|
|
1104
|
-
|
|
1110
|
+
|
|
1105
1111
|
call.microphone.setEnabled(hasPermissions)
|
|
1106
|
-
call.camera.setEnabled(hasPermissions)
|
|
1112
|
+
call.camera.setEnabled(hasPermissions && !isAudioOnly)
|
|
1107
1113
|
Log.d("StreamCallPlugin", "internalAcceptCall: Microphone and camera set to $hasPermissions for call ${call.id}")
|
|
1108
|
-
|
|
1114
|
+
|
|
1109
1115
|
Log.d("StreamCallPlugin", "internalAcceptCall: Setting CallContent with active call ${call.id}")
|
|
1110
1116
|
setOverlayContent(call)
|
|
1111
1117
|
Log.d("StreamCallPlugin", "internalAcceptCall: Content set for overlayView for call ${call.id}")
|
|
@@ -1117,7 +1123,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1117
1123
|
parent?.removeView(overlayView)
|
|
1118
1124
|
parent?.addView(overlayView, 0) // Add at index 0 to ensure it's behind other views
|
|
1119
1125
|
Log.d("StreamCallPlugin", "internalAcceptCall: OverlayView re-added to parent at index 0 for call ${call.id}")
|
|
1120
|
-
|
|
1126
|
+
|
|
1121
1127
|
// Add a small delay to ensure UI refresh
|
|
1122
1128
|
mainHandler.postDelayed({
|
|
1123
1129
|
Log.d("StreamCallPlugin", "internalAcceptCall: Delayed UI check, overlay visible: ${overlayView?.isVisible} for call ${call.id}")
|
|
@@ -1148,10 +1154,10 @@ class StreamCallPlugin : Plugin() {
|
|
|
1148
1154
|
pendingAcceptCall = call
|
|
1149
1155
|
Log.d("StreamCallPlugin", "internalAcceptCall: Set pendingAcceptCall to ${call.id}, resetting attempt count")
|
|
1150
1156
|
permissionAttemptCount = 0
|
|
1151
|
-
requestPermissions()
|
|
1157
|
+
requestPermissions(isAudioOnly)
|
|
1152
1158
|
}
|
|
1153
1159
|
}
|
|
1154
|
-
|
|
1160
|
+
|
|
1155
1161
|
} catch (e: Exception) {
|
|
1156
1162
|
Log.e("StreamCallPlugin", "internalAcceptCall: Error accepting call ${call.id}: ${e.message}", e)
|
|
1157
1163
|
runOnMainThread {
|
|
@@ -1166,10 +1172,17 @@ class StreamCallPlugin : Plugin() {
|
|
|
1166
1172
|
}
|
|
1167
1173
|
|
|
1168
1174
|
// Function to check required permissions
|
|
1169
|
-
private fun checkPermissions(): Boolean {
|
|
1170
|
-
Log.d("StreamCallPlugin", "checkPermissions: Entered")
|
|
1175
|
+
private fun checkPermissions(isAudioOnly: Boolean = false): Boolean {
|
|
1176
|
+
Log.d("StreamCallPlugin", "checkPermissions: Entered, isAudioOnly: $isAudioOnly")
|
|
1171
1177
|
val audioPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
|
|
1172
1178
|
Log.d("StreamCallPlugin", "checkPermissions: RECORD_AUDIO permission status: $audioPermission (Granted=${PackageManager.PERMISSION_GRANTED})")
|
|
1179
|
+
|
|
1180
|
+
if (isAudioOnly) {
|
|
1181
|
+
val allGranted = audioPermission == PackageManager.PERMISSION_GRANTED
|
|
1182
|
+
Log.d("StreamCallPlugin", "checkPermissions: Audio only call, all permissions granted: $allGranted")
|
|
1183
|
+
return allGranted
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1173
1186
|
val cameraPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
|
1174
1187
|
Log.d("StreamCallPlugin", "checkPermissions: CAMERA permission status: $cameraPermission (Granted=${PackageManager.PERMISSION_GRANTED})")
|
|
1175
1188
|
val allGranted = audioPermission == PackageManager.PERMISSION_GRANTED && cameraPermission == PackageManager.PERMISSION_GRANTED
|
|
@@ -1222,8 +1235,9 @@ class StreamCallPlugin : Plugin() {
|
|
|
1222
1235
|
// Determine what type of pending operation we have
|
|
1223
1236
|
val hasOutgoingCall = pendingCall != null && pendingCallUserIds != null
|
|
1224
1237
|
val hasActiveCallNeedingPermissions = pendingAcceptCall != null
|
|
1238
|
+
val hasPendingSetCamera = pendingSetCameraCall != null
|
|
1225
1239
|
|
|
1226
|
-
Log.d("StreamCallPlugin", "handlePermissionGranted: hasOutgoingCall=$hasOutgoingCall, hasActiveCallNeedingPermissions=$hasActiveCallNeedingPermissions")
|
|
1240
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: hasOutgoingCall=$hasOutgoingCall, hasActiveCallNeedingPermissions=$hasActiveCallNeedingPermissions, hasPendingSetCamera=$hasPendingSetCamera")
|
|
1227
1241
|
|
|
1228
1242
|
when {
|
|
1229
1243
|
hasOutgoingCall -> {
|
|
@@ -1246,7 +1260,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1246
1260
|
runOnMainThread {
|
|
1247
1261
|
try {
|
|
1248
1262
|
callToHandle.microphone.setEnabled(true)
|
|
1249
|
-
callToHandle.camera.setEnabled(
|
|
1263
|
+
callToHandle.camera.setEnabled(!this.callIsAudioOnly)
|
|
1250
1264
|
Log.d("StreamCallPlugin", "handlePermissionGranted: Camera and microphone enabled for call ${callToHandle.id}")
|
|
1251
1265
|
|
|
1252
1266
|
// Show success message
|
|
@@ -1282,7 +1296,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1282
1296
|
runOnMainThread {
|
|
1283
1297
|
try {
|
|
1284
1298
|
callToHandle.microphone.setEnabled(true)
|
|
1285
|
-
callToHandle.camera.setEnabled(
|
|
1299
|
+
callToHandle.camera.setEnabled(!this.callIsAudioOnly)
|
|
1286
1300
|
Log.d("StreamCallPlugin", "handlePermissionGranted: Camera and microphone enabled for stored call ${callToHandle.id}")
|
|
1287
1301
|
|
|
1288
1302
|
android.widget.Toast.makeText(
|
|
@@ -1298,6 +1312,32 @@ class StreamCallPlugin : Plugin() {
|
|
|
1298
1312
|
}
|
|
1299
1313
|
}
|
|
1300
1314
|
|
|
1315
|
+
hasPendingSetCamera -> {
|
|
1316
|
+
Log.d("StreamCallPlugin", "handlePermissionGranted: Handling pending setCameraEnabled call.")
|
|
1317
|
+
val callToHandle = pendingSetCameraCall!!
|
|
1318
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1319
|
+
|
|
1320
|
+
if (activeCall != null) {
|
|
1321
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1322
|
+
try {
|
|
1323
|
+
activeCall.camera.setEnabled(true)
|
|
1324
|
+
this@StreamCallPlugin.callIsAudioOnly = false
|
|
1325
|
+
callToHandle.resolve(JSObject().apply {
|
|
1326
|
+
put("success", true)
|
|
1327
|
+
})
|
|
1328
|
+
} catch (e: Exception) {
|
|
1329
|
+
Log.e("StreamCallPlugin", "Error enabling camera after permission grant", e)
|
|
1330
|
+
callToHandle.reject("Failed to enable camera after permission grant: ${e.message}")
|
|
1331
|
+
} finally {
|
|
1332
|
+
clearPendingCall()
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
} else {
|
|
1336
|
+
callToHandle.reject("No active call found to enable camera.")
|
|
1337
|
+
clearPendingCall()
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1301
1341
|
pendingCall != null -> {
|
|
1302
1342
|
// We have a pending call but unclear what type - fallback handling
|
|
1303
1343
|
Log.w("StreamCallPlugin", "handlePermissionGranted: Have pendingCall but unclear operation type")
|
|
@@ -1348,7 +1388,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1348
1388
|
} else if (permissionAttemptCount < 2) {
|
|
1349
1389
|
// Try asking again immediately if this is the first denial
|
|
1350
1390
|
Log.d("StreamCallPlugin", "handlePermissionDenied: First denial (attempt $permissionAttemptCount), asking again immediately")
|
|
1351
|
-
requestPermissions() // This will increment the attempt count
|
|
1391
|
+
requestPermissions(this.callIsAudioOnly) // This will increment the attempt count
|
|
1352
1392
|
} else {
|
|
1353
1393
|
// Second denial - show settings dialog (final ask)
|
|
1354
1394
|
Log.d("StreamCallPlugin", "handlePermissionDenied: Second denial (attempt $permissionAttemptCount), showing settings dialog (final ask)")
|
|
@@ -1371,7 +1411,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1371
1411
|
clearPendingCall()
|
|
1372
1412
|
|
|
1373
1413
|
// Execute the call creation logic
|
|
1374
|
-
createAndStartCall(call, userIds, callType, shouldRing, team, custom)
|
|
1414
|
+
createAndStartCall(call, userIds, callType, shouldRing, team, custom, this.callIsAudioOnly)
|
|
1375
1415
|
} else {
|
|
1376
1416
|
Log.w("StreamCallPlugin", "executePendingCall: Missing pending call data")
|
|
1377
1417
|
call?.reject("Internal error: missing call parameters")
|
|
@@ -1386,14 +1426,14 @@ class StreamCallPlugin : Plugin() {
|
|
|
1386
1426
|
pendingCallShouldRing = null
|
|
1387
1427
|
pendingCallTeam = null
|
|
1388
1428
|
pendingAcceptCall = null
|
|
1389
|
-
|
|
1429
|
+
pendingSetCameraCall = null
|
|
1390
1430
|
permissionAttemptCount = 0 // Reset attempt count when clearing
|
|
1391
1431
|
}
|
|
1392
1432
|
|
|
1393
1433
|
|
|
1394
1434
|
|
|
1395
1435
|
@OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
|
|
1396
|
-
private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?, custom: JSObject
|
|
1436
|
+
private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?, custom: JSObject?, isAudioOnly: Boolean) {
|
|
1397
1437
|
val selfUserId = streamVideoClient?.userId
|
|
1398
1438
|
if (selfUserId == null) {
|
|
1399
1439
|
call.reject("No self-user id found. Are you not logged in?")
|
|
@@ -1419,6 +1459,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1419
1459
|
custom = custom?.toMap() ?: emptyMap(),
|
|
1420
1460
|
ring = shouldRing,
|
|
1421
1461
|
team = team,
|
|
1462
|
+
video = !isAudioOnly
|
|
1422
1463
|
)
|
|
1423
1464
|
|
|
1424
1465
|
if (createResult?.isFailure == true) {
|
|
@@ -1429,7 +1470,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1429
1470
|
// Show overlay view
|
|
1430
1471
|
activity?.runOnUiThread {
|
|
1431
1472
|
streamCall?.microphone?.setEnabled(true)
|
|
1432
|
-
streamCall?.camera?.setEnabled(
|
|
1473
|
+
streamCall?.camera?.setEnabled(!isAudioOnly)
|
|
1433
1474
|
|
|
1434
1475
|
bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
|
|
1435
1476
|
bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
|
|
@@ -1453,21 +1494,27 @@ class StreamCallPlugin : Plugin() {
|
|
|
1453
1494
|
}
|
|
1454
1495
|
|
|
1455
1496
|
// Function to request required permissions
|
|
1456
|
-
private fun requestPermissions() {
|
|
1497
|
+
private fun requestPermissions(isAudioOnly: Boolean) {
|
|
1457
1498
|
permissionAttemptCount++
|
|
1458
|
-
Log.d("StreamCallPlugin", "requestPermissions: Attempt #$permissionAttemptCount - Requesting
|
|
1499
|
+
Log.d("StreamCallPlugin", "requestPermissions: Attempt #$permissionAttemptCount - Requesting permissions. isAudioOnly: $isAudioOnly")
|
|
1459
1500
|
|
|
1460
1501
|
// Record timing for instant denial detection
|
|
1461
1502
|
permissionRequestStartTime = System.currentTimeMillis()
|
|
1462
1503
|
Log.d("StreamCallPlugin", "requestPermissions: Starting permission request at $permissionRequestStartTime")
|
|
1463
1504
|
|
|
1505
|
+
val permissionsToRequest = if (isAudioOnly) {
|
|
1506
|
+
arrayOf(Manifest.permission.RECORD_AUDIO)
|
|
1507
|
+
} else {
|
|
1508
|
+
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1464
1511
|
ActivityCompat.requestPermissions(
|
|
1465
1512
|
activity,
|
|
1466
|
-
|
|
1513
|
+
permissionsToRequest,
|
|
1467
1514
|
9001 // Use high request code to avoid Capacitor conflicts
|
|
1468
1515
|
)
|
|
1469
1516
|
|
|
1470
|
-
Log.d("StreamCallPlugin", "requestPermissions: Permission request initiated with code 9001")
|
|
1517
|
+
Log.d("StreamCallPlugin", "requestPermissions: Permission request initiated with code 9001 for permissions: ${permissionsToRequest.joinToString()}")
|
|
1471
1518
|
}
|
|
1472
1519
|
|
|
1473
1520
|
private fun showPermissionSettingsDialog() {
|
|
@@ -1531,6 +1578,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1531
1578
|
|
|
1532
1579
|
val hasOutgoingCall = pendingCall != null && pendingCallUserIds != null
|
|
1533
1580
|
val hasIncomingCall = pendingCall != null && pendingAcceptCall != null
|
|
1581
|
+
val hasPendingSetCamera = pendingSetCameraCall != null
|
|
1534
1582
|
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1535
1583
|
|
|
1536
1584
|
when {
|
|
@@ -1541,6 +1589,12 @@ class StreamCallPlugin : Plugin() {
|
|
|
1541
1589
|
clearPendingCall()
|
|
1542
1590
|
}
|
|
1543
1591
|
|
|
1592
|
+
hasPendingSetCamera -> {
|
|
1593
|
+
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Rejecting pending setCameraEnabled call")
|
|
1594
|
+
pendingSetCameraCall?.reject("Camera permission is required to enable the camera.")
|
|
1595
|
+
clearPendingCall()
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1544
1598
|
hasIncomingCall && activeCall != null && activeCall.id == pendingAcceptCall?.id -> {
|
|
1545
1599
|
// Incoming call that's already active - DON'T end the call, just keep it without camera/mic
|
|
1546
1600
|
Log.d("StreamCallPlugin", "handleFinalPermissionDenial: Incoming call already active, keeping call without camera/mic")
|
|
@@ -1729,27 +1783,52 @@ class StreamCallPlugin : Plugin() {
|
|
|
1729
1783
|
return
|
|
1730
1784
|
}
|
|
1731
1785
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1786
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1787
|
+
if (activeCall == null) {
|
|
1788
|
+
call.reject("No active call")
|
|
1789
|
+
return
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (!enabled) {
|
|
1793
|
+
// Just disable, no permission needed
|
|
1794
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
1795
|
+
try {
|
|
1796
|
+
activeCall.camera.setEnabled(false)
|
|
1797
|
+
call.resolve(JSObject().apply {
|
|
1798
|
+
put("success", true)
|
|
1799
|
+
})
|
|
1800
|
+
} catch (e: Exception) {
|
|
1801
|
+
Log.e("StreamCallPlugin", "Error disabling camera: ${e.message}")
|
|
1802
|
+
call.reject("Failed to disable camera: ${e.message}")
|
|
1803
|
+
}
|
|
1737
1804
|
}
|
|
1805
|
+
return
|
|
1806
|
+
}
|
|
1738
1807
|
|
|
1808
|
+
// From here, enabled is true. We need to check for permission.
|
|
1809
|
+
val cameraPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
|
|
1810
|
+
if (cameraPermission == PackageManager.PERMISSION_GRANTED) {
|
|
1811
|
+
// Permission is already granted
|
|
1739
1812
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1740
1813
|
try {
|
|
1741
|
-
activeCall.
|
|
1814
|
+
activeCall.camera.setEnabled(true)
|
|
1815
|
+
// When we enable camera, the call is no longer audio-only
|
|
1816
|
+
this@StreamCallPlugin.callIsAudioOnly = false
|
|
1742
1817
|
call.resolve(JSObject().apply {
|
|
1743
1818
|
put("success", true)
|
|
1744
1819
|
})
|
|
1745
1820
|
} catch (e: Exception) {
|
|
1746
|
-
Log.e("StreamCallPlugin", "Error
|
|
1747
|
-
call.reject("Failed to
|
|
1821
|
+
Log.e("StreamCallPlugin", "Error enabling camera: ${e.message}")
|
|
1822
|
+
call.reject("Failed to enable camera: ${e.message}")
|
|
1748
1823
|
}
|
|
1749
1824
|
}
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1825
|
+
} else {
|
|
1826
|
+
// Permission is not granted, request it.
|
|
1827
|
+
Log.d("StreamCallPlugin", "Camera permission not granted. Requesting permission.")
|
|
1828
|
+
this.pendingSetCameraCall = call
|
|
1829
|
+
// we are enabling camera, so it's not an audio only call
|
|
1830
|
+
requestPermissions(false)
|
|
1831
|
+
// The call will be resolved/rejected in the permission result handlers
|
|
1753
1832
|
}
|
|
1754
1833
|
}
|
|
1755
1834
|
|
|
@@ -1768,7 +1847,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1768
1847
|
|
|
1769
1848
|
// Use call.state.totalParticipants to get participant count (as per StreamVideo Android SDK docs)
|
|
1770
1849
|
val totalParticipants = call.state.totalParticipants.value
|
|
1771
|
-
val shouldEndCall = isCreator || totalParticipants <=
|
|
1850
|
+
val shouldEndCall = isCreator || totalParticipants <= 2
|
|
1772
1851
|
|
|
1773
1852
|
Log.d("StreamCallPlugin", "Call $callId - Creator: $createdBy, CurrentUser: $currentUserId, IsCreator: $isCreator, TotalParticipants: $totalParticipants, ShouldEnd: $shouldEndCall")
|
|
1774
1853
|
|
|
@@ -1808,7 +1887,6 @@ class StreamCallPlugin : Plugin() {
|
|
|
1808
1887
|
val keyguardManager = ctx.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
1809
1888
|
if (keyguardManager.isKeyguardLocked) {
|
|
1810
1889
|
// we allow kill exclusively here
|
|
1811
|
-
// the idea is that:
|
|
1812
1890
|
// the 'empty' instance of this plugin class gets created in application
|
|
1813
1891
|
// then, it handles a notification and setts the context (this.savedContext)
|
|
1814
1892
|
// if the context is new
|
|
@@ -1958,6 +2036,9 @@ class StreamCallPlugin : Plugin() {
|
|
|
1958
2036
|
val shouldRing = call.getBoolean("ring") ?: true
|
|
1959
2037
|
val callId = java.util.UUID.randomUUID().toString()
|
|
1960
2038
|
val team = call.getString("team")
|
|
2039
|
+
val isAudioOnly = custom?.getBoolean("audio_only") ?: false
|
|
2040
|
+
this.callIsAudioOnly = isAudioOnly
|
|
2041
|
+
|
|
1961
2042
|
|
|
1962
2043
|
Log.d("StreamCallPlugin", "Creating call:")
|
|
1963
2044
|
Log.d("StreamCallPlugin", "- Call ID: $callId")
|
|
@@ -1969,7 +2050,7 @@ class StreamCallPlugin : Plugin() {
|
|
|
1969
2050
|
}
|
|
1970
2051
|
|
|
1971
2052
|
// Check permissions before creating the call
|
|
1972
|
-
if (!checkPermissions()) {
|
|
2053
|
+
if (!checkPermissions(isAudioOnly)) {
|
|
1973
2054
|
Log.d("StreamCallPlugin", "Permissions not granted, storing call parameters and requesting permissions")
|
|
1974
2055
|
// Store call parameters for later execution
|
|
1975
2056
|
pendingCall = call
|
|
@@ -1982,12 +2063,12 @@ class StreamCallPlugin : Plugin() {
|
|
|
1982
2063
|
}
|
|
1983
2064
|
// Reset attempt count for new permission flow
|
|
1984
2065
|
permissionAttemptCount = 0
|
|
1985
|
-
requestPermissions()
|
|
2066
|
+
requestPermissions(isAudioOnly)
|
|
1986
2067
|
return // Don't reject immediately, wait for permission result
|
|
1987
2068
|
}
|
|
1988
2069
|
|
|
1989
2070
|
// Execute call creation immediately if permissions are granted
|
|
1990
|
-
createAndStartCall(call, userIds, callType, shouldRing, team, custom)
|
|
2071
|
+
createAndStartCall(call, userIds, callType, shouldRing, team, custom, isAudioOnly)
|
|
1991
2072
|
} catch (e: Exception) {
|
|
1992
2073
|
call.reject("Failed to make call: ${e.message}")
|
|
1993
2074
|
}
|
|
@@ -2341,13 +2422,18 @@ class StreamCallPlugin : Plugin() {
|
|
|
2341
2422
|
fun joinCall(call: PluginCall) {
|
|
2342
2423
|
val fragment = callFragment
|
|
2343
2424
|
if (fragment != null && fragment.getCall() != null) {
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
}
|
|
2425
|
+
val activeCall = fragment.getCall()!!
|
|
2426
|
+
// I need to get custom data here, which is async.
|
|
2427
|
+
// The method is not suspend.
|
|
2428
|
+
// Let's launch a coroutine
|
|
2349
2429
|
CoroutineScope(Dispatchers.Main).launch {
|
|
2350
|
-
|
|
2430
|
+
val isAudioOnly = getIsAudioOnly(activeCall)
|
|
2431
|
+
if (!checkPermissions(isAudioOnly)) {
|
|
2432
|
+
requestPermissions(isAudioOnly)
|
|
2433
|
+
call.reject("Permissions required for call. Please grant them.")
|
|
2434
|
+
return@launch
|
|
2435
|
+
}
|
|
2436
|
+
activeCall.join()
|
|
2351
2437
|
call.resolve()
|
|
2352
2438
|
}
|
|
2353
2439
|
} else {
|
|
@@ -2386,7 +2472,9 @@ class StreamCallPlugin : Plugin() {
|
|
|
2386
2472
|
if (call != null) {
|
|
2387
2473
|
Log.d("StreamCallPlugin", "BroadcastReceiver: Accepting call with cid: $cid")
|
|
2388
2474
|
kotlinx.coroutines.GlobalScope.launch {
|
|
2389
|
-
|
|
2475
|
+
val isAudioOnly = getIsAudioOnly(call)
|
|
2476
|
+
this@StreamCallPlugin.callIsAudioOnly = isAudioOnly
|
|
2477
|
+
internalAcceptCall(call, requestPermissionsAfter = !checkPermissions(isAudioOnly))
|
|
2390
2478
|
}
|
|
2391
2479
|
bringAppToForeground()
|
|
2392
2480
|
} else {
|
|
@@ -2428,4 +2516,14 @@ class StreamCallPlugin : Plugin() {
|
|
|
2428
2516
|
private const val API_KEY_PREFS_NAME = "stream_video_api_key_prefs"
|
|
2429
2517
|
private const val DYNAMIC_API_KEY_PREF = "dynamic_api_key"
|
|
2430
2518
|
}
|
|
2519
|
+
|
|
2520
|
+
private suspend fun getIsAudioOnly(call: Call): Boolean {
|
|
2521
|
+
val callInfoResult = call.get()
|
|
2522
|
+
return if (callInfoResult.isSuccess) {
|
|
2523
|
+
val audioOnlyValue = callInfoResult.getOrNull()?.call?.custom?.get("audio_only")
|
|
2524
|
+
audioOnlyValue?.toString() == "true"
|
|
2525
|
+
} else {
|
|
2526
|
+
false
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2431
2529
|
}
|
|
@@ -869,7 +869,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
869
869
|
DispatchQueue.main.async {
|
|
870
870
|
Task { @MainActor in
|
|
871
871
|
self.callViewModel = CallViewModel(participantsLayout: .grid)
|
|
872
|
-
self.callViewModel?.participantAutoLeavePolicy = LastParticipantAutoLeavePolicy()
|
|
872
|
+
// self.callViewModel?.participantAutoLeavePolicy = LastParticipantAutoLeavePolicy()
|
|
873
873
|
|
|
874
874
|
// Setup subscriptions for new StreamVideo instance
|
|
875
875
|
self.setupActiveCallSubscription()
|
package/package.json
CHANGED