@capgo/capacitor-stream-call 0.0.25-alpha.0 → 0.0.27
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/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +93 -16
- package/ios/Sources/StreamCallPlugin/CallOverlayView.swift +1 -1
- package/ios/Sources/StreamCallPlugin/CustomVideoParticipantsView.swift +1 -1
- package/ios/Sources/StreamCallPlugin/ParticipantsView.swift +2 -2
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +49 -206
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
package ee.forgr.capacitor.streamcall
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import android.app.Application
|
|
4
4
|
import android.app.Notification
|
|
5
5
|
import android.app.NotificationManager
|
|
@@ -8,13 +8,17 @@ import android.content.Context
|
|
|
8
8
|
import android.content.Intent
|
|
9
9
|
import android.media.RingtoneManager
|
|
10
10
|
import android.os.Build
|
|
11
|
+
import android.util.Log
|
|
11
12
|
import androidx.core.app.NotificationCompat
|
|
13
|
+
import io.getstream.log.taggedLogger
|
|
14
|
+
import io.getstream.video.android.core.RingingState
|
|
12
15
|
import io.getstream.video.android.core.notifications.DefaultNotificationHandler
|
|
16
|
+
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
13
17
|
import io.getstream.video.android.model.StreamCallId
|
|
14
|
-
|
|
18
|
+
|
|
15
19
|
// declare "incoming_calls_custom" as a constant
|
|
16
20
|
const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
|
|
17
|
-
|
|
21
|
+
|
|
18
22
|
class CustomNotificationHandler(
|
|
19
23
|
val application: Application,
|
|
20
24
|
private val endCall: (callId: StreamCallId) -> Unit = {},
|
|
@@ -25,17 +29,89 @@ class CustomNotificationHandler(
|
|
|
25
29
|
private const val KEY_NOTIFICATION_TIME = "notification_creation_time"
|
|
26
30
|
}
|
|
27
31
|
private var allowSound = true;
|
|
28
|
-
|
|
29
|
-
override fun
|
|
32
|
+
|
|
33
|
+
override fun getRingingCallNotification(
|
|
34
|
+
ringingState: RingingState,
|
|
35
|
+
callId: StreamCallId,
|
|
36
|
+
callDisplayName: String?,
|
|
37
|
+
shouldHaveContentIntent: Boolean,
|
|
38
|
+
): Notification? {
|
|
39
|
+
return if (ringingState is RingingState.Incoming) {
|
|
40
|
+
val fullScreenPendingIntent = intentResolver.searchIncomingCallPendingIntent(callId)
|
|
41
|
+
val acceptCallPendingIntent = intentResolver.searchAcceptCallPendingIntent(callId)
|
|
42
|
+
val rejectCallPendingIntent = intentResolver.searchRejectCallPendingIntent(callId)
|
|
43
|
+
|
|
44
|
+
if (fullScreenPendingIntent != null && acceptCallPendingIntent != null && rejectCallPendingIntent != null) {
|
|
45
|
+
customGetIncomingCallNotification(
|
|
46
|
+
fullScreenPendingIntent,
|
|
47
|
+
acceptCallPendingIntent,
|
|
48
|
+
rejectCallPendingIntent,
|
|
49
|
+
callDisplayName,
|
|
50
|
+
shouldHaveContentIntent,
|
|
51
|
+
callId
|
|
52
|
+
)
|
|
53
|
+
} else {
|
|
54
|
+
Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
|
|
55
|
+
null
|
|
56
|
+
}
|
|
57
|
+
} else if (ringingState is RingingState.Outgoing) {
|
|
58
|
+
val outgoingCallPendingIntent = intentResolver.searchOutgoingCallPendingIntent(callId)
|
|
59
|
+
val endCallPendingIntent = intentResolver.searchEndCallPendingIntent(callId)
|
|
60
|
+
|
|
61
|
+
if (outgoingCallPendingIntent != null && endCallPendingIntent != null) {
|
|
62
|
+
getOngoingCallNotification(
|
|
63
|
+
callId,
|
|
64
|
+
callDisplayName,
|
|
65
|
+
isOutgoingCall = true,
|
|
66
|
+
)
|
|
67
|
+
} else {
|
|
68
|
+
Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
|
|
69
|
+
null
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun customGetIncomingCallNotification(
|
|
30
77
|
fullScreenPendingIntent: PendingIntent,
|
|
31
78
|
acceptCallPendingIntent: PendingIntent,
|
|
32
79
|
rejectCallPendingIntent: PendingIntent,
|
|
33
80
|
callerName: String?,
|
|
34
81
|
shouldHaveContentIntent: Boolean,
|
|
82
|
+
callId: StreamCallId
|
|
35
83
|
): Notification {
|
|
36
|
-
|
|
37
|
-
customCreateIncomingCallChannel()
|
|
38
|
-
|
|
84
|
+
|
|
85
|
+
// customCreateIncomingCallChannel()
|
|
86
|
+
val manufacturer = Build.MANUFACTURER.lowercase()
|
|
87
|
+
if (manufacturer.contains("xiaomi") || manufacturer.contains("mi")) {
|
|
88
|
+
// val serviceIntent = Intent(application, CallForegroundService::class.java)
|
|
89
|
+
// serviceIntent.action = CallForegroundService.ACTION_START_FOREGROUND_SERVICE
|
|
90
|
+
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
91
|
+
// application.startForegroundService(serviceIntent)
|
|
92
|
+
// } else {
|
|
93
|
+
// application.startService(serviceIntent)
|
|
94
|
+
// }
|
|
95
|
+
// Adjust PendingIntent for Xiaomi to avoid permission denial
|
|
96
|
+
val xiaomiAcceptIntent = PendingIntent.getActivity(
|
|
97
|
+
application,
|
|
98
|
+
0,
|
|
99
|
+
Intent("io.getstream.video.android.action.ACCEPT_CALL")
|
|
100
|
+
.setPackage(application.packageName)
|
|
101
|
+
.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId),
|
|
102
|
+
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
103
|
+
)
|
|
104
|
+
return buildNotification(
|
|
105
|
+
fullScreenPendingIntent,
|
|
106
|
+
xiaomiAcceptIntent,
|
|
107
|
+
rejectCallPendingIntent,
|
|
108
|
+
callerName,
|
|
109
|
+
shouldHaveContentIntent,
|
|
110
|
+
INCOMING_CALLS_CUSTOM,
|
|
111
|
+
true // Include sound
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
39
115
|
return buildNotification(
|
|
40
116
|
fullScreenPendingIntent,
|
|
41
117
|
acceptCallPendingIntent,
|
|
@@ -46,7 +122,7 @@ class CustomNotificationHandler(
|
|
|
46
122
|
true // Include sound
|
|
47
123
|
)
|
|
48
124
|
}
|
|
49
|
-
|
|
125
|
+
|
|
50
126
|
fun buildNotification(
|
|
51
127
|
fullScreenPendingIntent: PendingIntent,
|
|
52
128
|
acceptCallPendingIntent: PendingIntent,
|
|
@@ -64,10 +140,10 @@ class CustomNotificationHandler(
|
|
|
64
140
|
setOngoing(true)
|
|
65
141
|
setAutoCancel(false)
|
|
66
142
|
setCategory(NotificationCompat.CATEGORY_CALL)
|
|
67
|
-
|
|
143
|
+
|
|
68
144
|
// Clear all defaults first
|
|
69
145
|
setDefaults(0)
|
|
70
|
-
|
|
146
|
+
|
|
71
147
|
if (includeSound && allowSound) {
|
|
72
148
|
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
|
|
73
149
|
setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
|
|
@@ -75,7 +151,7 @@ class CustomNotificationHandler(
|
|
|
75
151
|
setSound(null)
|
|
76
152
|
setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
|
|
77
153
|
}
|
|
78
|
-
|
|
154
|
+
|
|
79
155
|
// setVibrate(longArrayOf(0, 1000, 500, 1000))
|
|
80
156
|
setLights(0xFF0000FF.toInt(), 1000, 1000)
|
|
81
157
|
setFullScreenIntent(fullScreenPendingIntent, true)
|
|
@@ -95,12 +171,12 @@ class CustomNotificationHandler(
|
|
|
95
171
|
// flags = flags or NotificationCompat.FLAG_ONGOING_EVENT
|
|
96
172
|
}
|
|
97
173
|
}
|
|
98
|
-
|
|
174
|
+
|
|
99
175
|
override fun onMissedCall(callId: StreamCallId, callDisplayName: String) {
|
|
100
176
|
endCall(callId)
|
|
101
177
|
super.onMissedCall(callId, callDisplayName)
|
|
102
178
|
}
|
|
103
|
-
|
|
179
|
+
|
|
104
180
|
private fun customCreateIncomingCallChannel() {
|
|
105
181
|
maybeCreateChannel(
|
|
106
182
|
channelId = INCOMING_CALLS_CUSTOM,
|
|
@@ -114,7 +190,7 @@ class CustomNotificationHandler(
|
|
|
114
190
|
importance = NotificationManager.IMPORTANCE_HIGH
|
|
115
191
|
this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
116
192
|
this.setShowBadge(true)
|
|
117
|
-
|
|
193
|
+
|
|
118
194
|
// Set the channel to be silent since we handle sound via RingtonePlayer
|
|
119
195
|
setSound(null, null)
|
|
120
196
|
enableVibration(true)
|
|
@@ -126,8 +202,9 @@ class CustomNotificationHandler(
|
|
|
126
202
|
},
|
|
127
203
|
)
|
|
128
204
|
}
|
|
129
|
-
|
|
205
|
+
|
|
130
206
|
public fun clone(): CustomNotificationHandler {
|
|
131
207
|
return CustomNotificationHandler(this.application, this.endCall, this.incomingCall)
|
|
132
208
|
}
|
|
133
209
|
}
|
|
210
|
+
|
|
@@ -57,7 +57,7 @@ struct CallOverlayView: View {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
private func changeTrackVisibility(_ participant: CallParticipant?, isVisible: Bool) {
|
|
60
|
-
|
|
60
|
+
print("changeTrackVisibility for \(String(describing: participant?.userId)), visible: \(isVisible)")
|
|
61
61
|
guard let participant = participant,
|
|
62
62
|
let call = viewModel.call else { return }
|
|
63
63
|
Task {
|
|
@@ -29,7 +29,7 @@ public struct CustomVideoCallParticipantView<Factory: ViewFactory>: View {
|
|
|
29
29
|
customData: [String: RawJSON],
|
|
30
30
|
call: Call?
|
|
31
31
|
) {
|
|
32
|
-
|
|
32
|
+
print("size: \(availableFrame.size)")
|
|
33
33
|
self.viewFactory = viewFactory
|
|
34
34
|
self.participant = participant
|
|
35
35
|
self.id = id ?? participant.id
|
|
@@ -34,8 +34,8 @@ extension View {
|
|
|
34
34
|
value: [ViewFramePreferenceData(label: label,
|
|
35
35
|
frame: geo.frame(in: .global))])
|
|
36
36
|
.onAppear {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
print("ParticipantsView - Collecting frame for label: \(label)")
|
|
38
|
+
print("Frame: \(geo.frame(in: .global))")
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
)
|
|
@@ -5,7 +5,6 @@ import StreamVideoSwiftUI
|
|
|
5
5
|
import SwiftUI
|
|
6
6
|
import Combine
|
|
7
7
|
import WebKit
|
|
8
|
-
import os.log
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Please read the Capacitor iOS Plugin Development Guide
|
|
@@ -28,15 +27,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
28
27
|
CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise)
|
|
29
28
|
]
|
|
30
29
|
|
|
31
|
-
// OSLog for streamcall plugin
|
|
32
|
-
private static let osLog = OSLog(subsystem: "com.example.plugin.streamcall", category: "StreamCallPlugin")
|
|
33
|
-
|
|
34
|
-
// Static function for dual printing to console and os.log
|
|
35
|
-
public static func dualprint(_ message: String) {
|
|
36
|
-
print(message)
|
|
37
|
-
os_log("%{public}@", log: osLog, type: .info, message)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
30
|
private enum State {
|
|
41
31
|
case notInitialized
|
|
42
32
|
case initializing
|
|
@@ -44,8 +34,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
44
34
|
}
|
|
45
35
|
private var apiKey: String?
|
|
46
36
|
private var state: State = .notInitialized
|
|
47
|
-
private static let tokenRefreshQueue = DispatchQueue(label: "stream.call.token.refresh")
|
|
48
|
-
private static let tokenRefreshSemaphore = DispatchSemaphore(value: 1)
|
|
49
37
|
private var currentToken: String?
|
|
50
38
|
private var tokenWaitSemaphore: DispatchSemaphore?
|
|
51
39
|
|
|
@@ -66,9 +54,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
66
54
|
@Injected(\.callKitPushNotificationAdapter) var callKitPushNotificationAdapter
|
|
67
55
|
private var webviewDelegate: WebviewNavigationDelegate?
|
|
68
56
|
|
|
69
|
-
// Add class property to store call states
|
|
70
|
-
private var callStates: [String: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)] = [:]
|
|
71
|
-
|
|
72
57
|
// Declare as optional and initialize in load() method
|
|
73
58
|
private var callViewModel: CallViewModel?
|
|
74
59
|
|
|
@@ -107,7 +92,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
107
92
|
|
|
108
93
|
// Check if we have a logged in user for handling incoming calls
|
|
109
94
|
if let credentials = SecureUserRepository.shared.loadCurrentUser() {
|
|
110
|
-
|
|
95
|
+
print("Loading user for StreamCallPlugin: \(credentials.user.name)")
|
|
111
96
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
112
97
|
self.initializeStreamVideo()
|
|
113
98
|
}
|
|
@@ -118,7 +103,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
118
103
|
wrappedDelegate: self.webView?.navigationDelegate,
|
|
119
104
|
onSetupOverlay: { [weak self] in
|
|
120
105
|
guard let self = self else { return }
|
|
121
|
-
|
|
106
|
+
print("Attempting to setup call view")
|
|
122
107
|
|
|
123
108
|
self.setupViews()
|
|
124
109
|
}
|
|
@@ -138,7 +123,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
138
123
|
return
|
|
139
124
|
}
|
|
140
125
|
|
|
141
|
-
|
|
126
|
+
print("loginMagicToken received token")
|
|
142
127
|
currentToken = token
|
|
143
128
|
tokenWaitSemaphore?.signal()
|
|
144
129
|
call.resolve()
|
|
@@ -153,17 +138,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
153
138
|
guard let self = self else { return }
|
|
154
139
|
Task {
|
|
155
140
|
do {
|
|
156
|
-
|
|
141
|
+
print("Setting up token subscription")
|
|
157
142
|
try self.requireInitialized()
|
|
158
143
|
if let lastVoIPToken = self.lastVoIPToken, !lastVoIPToken.isEmpty {
|
|
159
|
-
|
|
144
|
+
print("Deleting device: \(lastVoIPToken)")
|
|
160
145
|
try await self.streamVideo?.deleteDevice(id: lastVoIPToken)
|
|
161
146
|
}
|
|
162
147
|
if !updatedDeviceToken.isEmpty {
|
|
163
|
-
|
|
148
|
+
print("Setting voip device: \(updatedDeviceToken)")
|
|
164
149
|
try await self.streamVideo?.setVoipDevice(id: updatedDeviceToken)
|
|
165
150
|
// Save the token to our secure storage
|
|
166
|
-
|
|
151
|
+
print("Saving voip token: \(updatedDeviceToken)")
|
|
167
152
|
SecureUserRepository.shared.save(voipPushToken: updatedDeviceToken)
|
|
168
153
|
}
|
|
169
154
|
self.lastVoIPToken = updatedDeviceToken
|
|
@@ -185,7 +170,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
185
170
|
|
|
186
171
|
// Verify callViewModel exists
|
|
187
172
|
guard let callViewModel = self.callViewModel, let streamVideo = self.streamVideo else {
|
|
188
|
-
|
|
173
|
+
print("Warning: setupActiveCallSubscription called but callViewModel or streamVideo is nil")
|
|
189
174
|
// Schedule a retry after a short delay if callViewModel is nil
|
|
190
175
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
|
191
176
|
self?.setupActiveCallSubscription()
|
|
@@ -193,7 +178,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
193
178
|
return
|
|
194
179
|
}
|
|
195
180
|
|
|
196
|
-
|
|
181
|
+
print("Setting up active call subscription")
|
|
197
182
|
|
|
198
183
|
// Create a strong reference to callViewModel to ensure it's not deallocated
|
|
199
184
|
// while the subscription is active
|
|
@@ -205,7 +190,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
205
190
|
.sink { [weak self, weak viewModel] activeCall in
|
|
206
191
|
guard let self = self, let viewModel = viewModel else { return }
|
|
207
192
|
|
|
208
|
-
|
|
193
|
+
print("Active call update from streamVideo: \(String(describing: activeCall?.cId))")
|
|
209
194
|
|
|
210
195
|
if let activeCall = activeCall {
|
|
211
196
|
// Sync callViewModel with activeCall from streamVideo state
|
|
@@ -222,24 +207,24 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
222
207
|
.receive(on: DispatchQueue.main)
|
|
223
208
|
.sink { [weak self, weak viewModel] newState in
|
|
224
209
|
guard let self = self, let viewModel = viewModel else {
|
|
225
|
-
|
|
210
|
+
print("Warning: Call state update received but self or viewModel is nil")
|
|
226
211
|
return
|
|
227
212
|
}
|
|
228
213
|
|
|
229
214
|
do {
|
|
230
215
|
try self.requireInitialized()
|
|
231
|
-
|
|
216
|
+
print("Call State Update: \(newState)")
|
|
232
217
|
|
|
233
218
|
if newState == .inCall {
|
|
234
|
-
|
|
235
|
-
|
|
219
|
+
print("- In call state detected")
|
|
220
|
+
print("- All participants: \(String(describing: viewModel.participants))")
|
|
236
221
|
|
|
237
222
|
// Create/update overlay and make visible when there's an active call
|
|
238
223
|
self.createCallOverlayView()
|
|
239
224
|
|
|
240
225
|
// Notify that a call has started - but only if we haven't notified for this call yet
|
|
241
226
|
if let callId = viewModel.call?.cId, !self.hasNotifiedCallJoined || callId != self.currentCallId {
|
|
242
|
-
|
|
227
|
+
print("Notifying call joined: \(callId)")
|
|
243
228
|
self.updateCallStatusAndNotify(callId: callId, state: "joined")
|
|
244
229
|
self.hasNotifiedCallJoined = true
|
|
245
230
|
}
|
|
@@ -248,7 +233,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
248
233
|
} else if newState == .idle && self.streamVideo?.state.activeCall == nil {
|
|
249
234
|
// Get the call ID that was active before the state changed
|
|
250
235
|
let endingCallId = viewModel.call?.cId
|
|
251
|
-
|
|
236
|
+
print("Call ending: \(String(describing: endingCallId))")
|
|
252
237
|
|
|
253
238
|
// Notify that call has ended - use the properly tracked call ID
|
|
254
239
|
self.updateCallStatusAndNotify(callId: endingCallId ?? "", state: "left")
|
|
@@ -256,17 +241,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
256
241
|
// Reset notification flag when call ends
|
|
257
242
|
self.hasNotifiedCallJoined = false
|
|
258
243
|
|
|
259
|
-
// Clean up any resources for this call
|
|
260
|
-
if let callCid = endingCallId {
|
|
261
|
-
// Invalidate and remove the timer
|
|
262
|
-
self.callStates[callCid]?.timer?.invalidate()
|
|
263
|
-
|
|
264
|
-
// Remove call from callStates
|
|
265
|
-
self.callStates.removeValue(forKey: callCid)
|
|
266
|
-
|
|
267
|
-
StreamCallPlugin.dualprint("Cleaned up resources for ended call: \(callCid)")
|
|
268
|
-
}
|
|
269
|
-
|
|
270
244
|
// Remove the call overlay view when not in a call
|
|
271
245
|
self.ensureViewRemoved()
|
|
272
246
|
}
|
|
@@ -281,7 +255,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
281
255
|
statePublisher.cancel()
|
|
282
256
|
}
|
|
283
257
|
|
|
284
|
-
|
|
258
|
+
print("Active call subscription setup completed")
|
|
285
259
|
|
|
286
260
|
// Schedule a periodic check to ensure subscription is active
|
|
287
261
|
self.scheduleSubscriptionCheck()
|
|
@@ -296,7 +270,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
296
270
|
|
|
297
271
|
// Check if we're in a state where we need the subscription but it's not active
|
|
298
272
|
if self.state == .initialized && self.activeCallSubscription == nil && self.callViewModel != nil {
|
|
299
|
-
|
|
273
|
+
print("Subscription check: Restoring lost activeCallSubscription")
|
|
300
274
|
self.setupActiveCallSubscription()
|
|
301
275
|
} else {
|
|
302
276
|
// Schedule the next check
|
|
@@ -305,134 +279,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
305
279
|
}
|
|
306
280
|
}
|
|
307
281
|
|
|
308
|
-
@objc private func checkCallTimeoutTimer(_ timer: Timer) {
|
|
309
|
-
guard let callCid = timer.userInfo as? String else { return }
|
|
310
|
-
|
|
311
|
-
Task { [weak self] in
|
|
312
|
-
guard let self = self else { return }
|
|
313
|
-
await self.checkCallTimeout(callCid: callCid)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
private func checkCallTimeout(callCid: String) async {
|
|
318
|
-
// Get a local copy of the call state from the main thread
|
|
319
|
-
let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
|
|
320
|
-
return self.callStates[callCid]
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
guard let callState = callState else { return }
|
|
324
|
-
|
|
325
|
-
// Calculate time elapsed since call creation
|
|
326
|
-
let now = Date()
|
|
327
|
-
let elapsedSeconds = now.timeIntervalSince(callState.createdAt)
|
|
328
|
-
|
|
329
|
-
// Check if 30 seconds have passed
|
|
330
|
-
if elapsedSeconds >= 30.0 {
|
|
331
|
-
|
|
332
|
-
// Check if anyone has accepted
|
|
333
|
-
let hasAccepted = callState.participantResponses.values.contains { $0 == "accepted" }
|
|
334
|
-
|
|
335
|
-
if !hasAccepted {
|
|
336
|
-
StreamCallPlugin.dualprint("Call \(callCid) has timed out after \(elapsedSeconds) seconds")
|
|
337
|
-
StreamCallPlugin.dualprint("No one accepted call \(callCid), marking all non-responders as missed")
|
|
338
|
-
|
|
339
|
-
// Mark all members who haven't responded as "missed"
|
|
340
|
-
for member in callState.members {
|
|
341
|
-
let memberId = member.userId
|
|
342
|
-
let needsToBeMarkedAsMissed = await MainActor.run {
|
|
343
|
-
return self.callStates[callCid]?.participantResponses[memberId] == nil
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if needsToBeMarkedAsMissed {
|
|
347
|
-
// Update callStates map on main thread
|
|
348
|
-
await MainActor.run {
|
|
349
|
-
var updatedCallState = self.callStates[callCid]
|
|
350
|
-
updatedCallState?.participantResponses[memberId] = "missed"
|
|
351
|
-
if let updatedCallState = updatedCallState {
|
|
352
|
-
self.callStates[callCid] = updatedCallState
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Notify listeners
|
|
357
|
-
await MainActor.run {
|
|
358
|
-
self.updateCallStatusAndNotify(callId: callCid, state: "missed", userId: memberId)
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// End the call
|
|
364
|
-
if let call = streamVideo?.state.activeCall, call.cId == callCid {
|
|
365
|
-
call.leave()
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Clean up timer on main thread
|
|
369
|
-
await MainActor.run {
|
|
370
|
-
self.callStates[callCid]?.timer?.invalidate()
|
|
371
|
-
var updatedCallState = self.callStates[callCid]
|
|
372
|
-
updatedCallState?.timer = nil
|
|
373
|
-
if let updatedCallState = updatedCallState {
|
|
374
|
-
self.callStates[callCid] = updatedCallState
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Remove from callStates
|
|
378
|
-
self.callStates.removeValue(forKey: callCid)
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Update UI
|
|
382
|
-
await MainActor.run {
|
|
383
|
-
// self.overlayViewModel?.updateCall(nil)
|
|
384
|
-
self.overlayView?.isHidden = true
|
|
385
|
-
self.webView?.isOpaque = true
|
|
386
|
-
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "timeout")
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
private func checkAllParticipantsResponded(callCid: String) async {
|
|
393
|
-
// Get a local copy of the call state from the main thread
|
|
394
|
-
let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
|
|
395
|
-
return self.callStates[callCid]
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
guard let callState = callState else {
|
|
399
|
-
StreamCallPlugin.dualprint("Call state not found for cId: \(callCid)")
|
|
400
|
-
return
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
let totalParticipants = callState.members.count
|
|
404
|
-
let responseCount = callState.participantResponses.count
|
|
405
|
-
|
|
406
|
-
StreamCallPlugin.dualprint("Total participants: \(totalParticipants), Responses: \(responseCount)")
|
|
407
|
-
|
|
408
|
-
let allResponded = responseCount >= totalParticipants
|
|
409
|
-
let allRejectedOrMissed = allResponded &&
|
|
410
|
-
callState.participantResponses.values.allSatisfy { $0 == "rejected" || $0 == "missed" }
|
|
411
|
-
|
|
412
|
-
if allResponded && allRejectedOrMissed {
|
|
413
|
-
StreamCallPlugin.dualprint("All participants have rejected or missed the call")
|
|
414
|
-
|
|
415
|
-
// End the call
|
|
416
|
-
if let call = streamVideo?.state.activeCall, call.cId == callCid {
|
|
417
|
-
call.leave()
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Clean up timer and remove from callStates on main thread
|
|
421
|
-
await MainActor.run {
|
|
422
|
-
// Clean up timer
|
|
423
|
-
self.callStates[callCid]?.timer?.invalidate()
|
|
424
|
-
|
|
425
|
-
// Remove from callStates
|
|
426
|
-
self.callStates.removeValue(forKey: callCid)
|
|
427
|
-
|
|
428
|
-
// self.overlayViewModel?.updateCall(nil)
|
|
429
|
-
self.overlayView?.isHidden = true
|
|
430
|
-
self.webView?.isOpaque = true
|
|
431
|
-
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "all_rejected_or_missed")
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
282
|
@objc func login(_ call: CAPPluginCall) {
|
|
437
283
|
guard let token = call.getString("token"),
|
|
438
284
|
let userId = call.getString("userId"),
|
|
@@ -554,30 +400,26 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
554
400
|
|
|
555
401
|
Task {
|
|
556
402
|
do {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
403
|
+
print("Creating call:")
|
|
404
|
+
print("- Call ID: \(callId)")
|
|
405
|
+
print("- Call Type: \(callType)")
|
|
406
|
+
print("- Users: \(members)")
|
|
407
|
+
print("- Should Ring: \(shouldRing)")
|
|
408
|
+
print("- Team: \(team)")
|
|
563
409
|
|
|
564
|
-
// Create the call object
|
|
565
|
-
let streamCall = streamVideo?.call(callType: callType, callId: callId)
|
|
566
|
-
|
|
567
|
-
// Start the call with the members
|
|
568
|
-
StreamCallPlugin.dualprint("Creating call with members...")
|
|
569
|
-
try await streamCall?.create(
|
|
570
|
-
memberIds: members,
|
|
571
|
-
custom: [:],
|
|
572
|
-
team: team, ring: shouldRing
|
|
573
|
-
)
|
|
574
410
|
|
|
575
|
-
// Join the call
|
|
576
|
-
StreamCallPlugin.dualprint("Joining call...")
|
|
577
|
-
try await streamCall?.join(create: false)
|
|
578
|
-
StreamCallPlugin.dualprint("Successfully joined call")
|
|
579
411
|
|
|
580
412
|
// Update the CallOverlayView with the active call
|
|
413
|
+
// Create the call object
|
|
414
|
+
await self.callViewModel?.startCall(
|
|
415
|
+
callType: callType,
|
|
416
|
+
callId: callId,
|
|
417
|
+
members: members.map { Member(userId: $0, role: nil, customData: [:], updatedAt: nil) },
|
|
418
|
+
ring: shouldRing
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
// Update UI on main thread
|
|
581
423
|
await MainActor.run {
|
|
582
424
|
// self.overlayViewModel?.updateCall(streamCall)
|
|
583
425
|
self.overlayView?.isHidden = false
|
|
@@ -586,7 +428,8 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
586
428
|
|
|
587
429
|
call.resolve([
|
|
588
430
|
"success": true
|
|
589
|
-
])
|
|
431
|
+
])
|
|
432
|
+
|
|
590
433
|
} catch {
|
|
591
434
|
log.error("Error making call: \(String(describing: error))")
|
|
592
435
|
call.reject("Failed to make call: \(error.localizedDescription)")
|
|
@@ -705,14 +548,14 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
705
548
|
}
|
|
706
549
|
|
|
707
550
|
// Join the call
|
|
708
|
-
|
|
551
|
+
print("Accepting and joining call \(streamCall!.cId)...")
|
|
709
552
|
guard case .incoming(let incomingCall) = await self.callViewModel?.callingState else {
|
|
710
553
|
call.reject("Failed to accept call as there is no call ID")
|
|
711
554
|
return
|
|
712
555
|
}
|
|
713
556
|
|
|
714
557
|
await self.callViewModel?.acceptCall(callType: incomingCall.type, callId: incomingCall.id)
|
|
715
|
-
|
|
558
|
+
print("Successfully joined call")
|
|
716
559
|
|
|
717
560
|
// Update the CallOverlayView with the active call
|
|
718
561
|
await MainActor.run {
|
|
@@ -736,18 +579,18 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
736
579
|
|
|
737
580
|
private func initializeStreamVideo() {
|
|
738
581
|
if (state == .initialized) {
|
|
739
|
-
|
|
582
|
+
print("initializeStreamVideo already initialized")
|
|
740
583
|
// Try to get user credentials from repository
|
|
741
584
|
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
|
|
742
|
-
|
|
585
|
+
print("Save credentials not found, skipping initialization")
|
|
743
586
|
return
|
|
744
587
|
}
|
|
745
588
|
if (savedCredentials.user.id == streamVideo?.user.id) {
|
|
746
|
-
|
|
589
|
+
print("Skipping initializeStreamVideo as user is already logged in")
|
|
747
590
|
return
|
|
748
591
|
}
|
|
749
592
|
} else if (state == .initializing) {
|
|
750
|
-
|
|
593
|
+
print("initializeStreamVideo rejected - already initializing")
|
|
751
594
|
return
|
|
752
595
|
}
|
|
753
596
|
state = .initializing
|
|
@@ -755,11 +598,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
755
598
|
// Try to get user credentials from repository
|
|
756
599
|
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser(),
|
|
757
600
|
let apiKey = self.apiKey else {
|
|
758
|
-
|
|
601
|
+
print("No saved credentials or API key found, skipping initialization")
|
|
759
602
|
state = .notInitialized
|
|
760
603
|
return
|
|
761
604
|
}
|
|
762
|
-
|
|
605
|
+
print("Initializing with saved credentials for user: \(savedCredentials.user.name)")
|
|
763
606
|
|
|
764
607
|
LogConfig.level = .debug
|
|
765
608
|
self.streamVideo = StreamVideo(
|
|
@@ -768,7 +611,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
768
611
|
token: UserToken(stringLiteral: savedCredentials.tokenValue),
|
|
769
612
|
tokenProvider: {completion in
|
|
770
613
|
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
|
|
771
|
-
|
|
614
|
+
print("No saved credentials or API key found, cannot refresh token")
|
|
772
615
|
|
|
773
616
|
completion(.failure(NSError(domain: "No saved credentials or API key found, cannot refresh token", code: 0, userInfo: nil)))
|
|
774
617
|
return
|
|
@@ -861,7 +704,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
861
704
|
|
|
862
705
|
// Check if we already have an overlay view - do nothing if it exists
|
|
863
706
|
if let existingOverlayView = self.overlayView, existingOverlayView.superview != nil {
|
|
864
|
-
|
|
707
|
+
print("Call overlay view already exists, making it visible")
|
|
865
708
|
existingOverlayView.isHidden = false
|
|
866
709
|
// Make webview transparent to see StreamCall UI underneath
|
|
867
710
|
webView.isOpaque = false
|
|
@@ -870,7 +713,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
870
713
|
return
|
|
871
714
|
}
|
|
872
715
|
|
|
873
|
-
|
|
716
|
+
print("Creating new call overlay view")
|
|
874
717
|
|
|
875
718
|
// First, create the overlay view
|
|
876
719
|
let overlayView = UIHostingController(rootView: CallOverlayView(viewModel: callOverlayView))
|
|
@@ -923,7 +766,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
923
766
|
private func ensureViewRemoved() {
|
|
924
767
|
// Check if we have an overlay view
|
|
925
768
|
if let existingOverlayView = self.overlayView {
|
|
926
|
-
|
|
769
|
+
print("Hiding call overlay view")
|
|
927
770
|
|
|
928
771
|
// Hide the view instead of removing it
|
|
929
772
|
existingOverlayView.isHidden = true
|
|
@@ -933,7 +776,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
933
776
|
self.webView?.backgroundColor = nil
|
|
934
777
|
self.webView?.scrollView.backgroundColor = nil
|
|
935
778
|
} else {
|
|
936
|
-
|
|
779
|
+
print("No call overlay view to hide")
|
|
937
780
|
}
|
|
938
781
|
}
|
|
939
782
|
|
package/package.json
CHANGED