@capgo/capacitor-stream-call 0.0.6 → 0.0.19
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 +265 -11
- package/android/build.gradle +22 -4
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +2 -2
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +179 -205
- package/dist/docs.json +761 -12
- package/dist/esm/definitions.d.ts +57 -23
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +58 -22
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +58 -22
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +58 -22
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +167 -144
- package/ios/Sources/StreamCallPlugin/TouchInterceptView.swift +3 -3
- package/package.json +2 -2
|
@@ -23,9 +23,10 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
23
23
|
CAPPluginMethod(name: "setMicrophoneEnabled", returnType: CAPPluginReturnPromise),
|
|
24
24
|
CAPPluginMethod(name: "setCameraEnabled", returnType: CAPPluginReturnPromise),
|
|
25
25
|
CAPPluginMethod(name: "acceptCall", returnType: CAPPluginReturnPromise),
|
|
26
|
-
CAPPluginMethod(name: "isCameraEnabled", returnType: CAPPluginReturnPromise)
|
|
26
|
+
CAPPluginMethod(name: "isCameraEnabled", returnType: CAPPluginReturnPromise),
|
|
27
|
+
CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise)
|
|
27
28
|
]
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
private enum State {
|
|
30
31
|
case notInitialized
|
|
31
32
|
case initializing
|
|
@@ -37,7 +38,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
37
38
|
private static let tokenRefreshSemaphore = DispatchSemaphore(value: 1)
|
|
38
39
|
private var currentToken: String?
|
|
39
40
|
private var tokenWaitSemaphore: DispatchSemaphore?
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
private var overlayView: UIView?
|
|
42
43
|
private var hostingController: UIHostingController<CallOverlayView>?
|
|
43
44
|
private var overlayViewModel: CallOverlayViewModel?
|
|
@@ -45,19 +46,47 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
45
46
|
private var activeCallSubscription: AnyCancellable?
|
|
46
47
|
private var lastVoIPToken: String?
|
|
47
48
|
private var touchInterceptView: TouchInterceptView?
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
private var streamVideo: StreamVideo?
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
// Track the current active call ID
|
|
52
53
|
private var currentActiveCallId: String?
|
|
53
|
-
|
|
54
|
+
|
|
55
|
+
// Store current call info for getCallStatus
|
|
56
|
+
private var currentCallId: String = ""
|
|
57
|
+
private var currentCallState: String = ""
|
|
58
|
+
|
|
54
59
|
@Injected(\.callKitAdapter) var callKitAdapter
|
|
55
60
|
@Injected(\.callKitPushNotificationAdapter) var callKitPushNotificationAdapter
|
|
56
61
|
private var webviewDelegate: WebviewNavigationDelegate?
|
|
57
|
-
|
|
62
|
+
|
|
58
63
|
// Add class property to store call states
|
|
59
64
|
private var callStates: [String: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)] = [:]
|
|
60
|
-
|
|
65
|
+
|
|
66
|
+
// Helper method to update call status and notify listeners
|
|
67
|
+
private func updateCallStatusAndNotify(callId: String, state: String, userId: String? = nil, reason: String? = nil) {
|
|
68
|
+
// Update stored call info
|
|
69
|
+
currentCallId = callId
|
|
70
|
+
currentCallState = state
|
|
71
|
+
|
|
72
|
+
// Create data dictionary with only the fields in the CallEvent interface
|
|
73
|
+
var data: [String: Any] = [
|
|
74
|
+
"callId": callId,
|
|
75
|
+
"state": state
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
if let userId = userId {
|
|
79
|
+
data["userId"] = userId
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if let reason = reason {
|
|
83
|
+
data["reason"] = reason
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Notify listeners
|
|
87
|
+
notifyListeners("callEvent", data: data)
|
|
88
|
+
}
|
|
89
|
+
|
|
61
90
|
override public func load() {
|
|
62
91
|
// Read API key from Info.plist
|
|
63
92
|
if let apiKey = Bundle.main.object(forInfoDictionaryKey: "CAPACITOR_STREAM_VIDEO_APIKEY") as? String {
|
|
@@ -66,7 +95,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
66
95
|
if self.apiKey == nil {
|
|
67
96
|
fatalError("Cannot get apikey")
|
|
68
97
|
}
|
|
69
|
-
|
|
98
|
+
|
|
70
99
|
// Check if we have a logged in user for handling incoming calls
|
|
71
100
|
if let credentials = SecureUserRepository.shared.loadCurrentUser() {
|
|
72
101
|
print("Loading user for StreamCallPlugin: \(credentials.user.name)")
|
|
@@ -74,21 +103,21 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
74
103
|
self.initializeStreamVideo()
|
|
75
104
|
}
|
|
76
105
|
}
|
|
77
|
-
|
|
106
|
+
|
|
78
107
|
// Create and set the navigation delegate
|
|
79
108
|
self.webviewDelegate = WebviewNavigationDelegate(
|
|
80
109
|
wrappedDelegate: self.webView?.navigationDelegate,
|
|
81
110
|
onSetupOverlay: { [weak self] in
|
|
82
111
|
guard let self = self else { return }
|
|
83
112
|
print("Attempting to setup call view")
|
|
84
|
-
|
|
113
|
+
|
|
85
114
|
self.setupViews()
|
|
86
115
|
}
|
|
87
116
|
)
|
|
88
|
-
|
|
117
|
+
|
|
89
118
|
self.webView?.navigationDelegate = self.webviewDelegate
|
|
90
119
|
}
|
|
91
|
-
|
|
120
|
+
|
|
92
121
|
// private func cleanupStreamVideo() {
|
|
93
122
|
// // Cancel subscriptions
|
|
94
123
|
// tokenSubscription?.cancel()
|
|
@@ -114,7 +143,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
114
143
|
//
|
|
115
144
|
// state = .notInitialized
|
|
116
145
|
// }
|
|
117
|
-
|
|
146
|
+
|
|
118
147
|
private func requireInitialized() throws {
|
|
119
148
|
guard state == .initialized else {
|
|
120
149
|
throw NSError(domain: "StreamCallPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "StreamVideo not initialized"])
|
|
@@ -125,17 +154,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
125
154
|
call.reject("Missing token parameter")
|
|
126
155
|
return
|
|
127
156
|
}
|
|
128
|
-
|
|
157
|
+
|
|
129
158
|
print("loginMagicToken received token")
|
|
130
159
|
currentToken = token
|
|
131
160
|
tokenWaitSemaphore?.signal()
|
|
132
161
|
call.resolve()
|
|
133
162
|
}
|
|
134
|
-
|
|
163
|
+
|
|
135
164
|
private func setupTokenSubscription() {
|
|
136
165
|
// Cancel existing subscription if any
|
|
137
166
|
tokenSubscription?.cancel()
|
|
138
|
-
|
|
167
|
+
|
|
139
168
|
// Create new subscription
|
|
140
169
|
tokenSubscription = callKitPushNotificationAdapter.$deviceToken.sink { [weak self] (updatedDeviceToken: String) in
|
|
141
170
|
guard let self = self else { return }
|
|
@@ -161,26 +190,23 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
161
190
|
}
|
|
162
191
|
}
|
|
163
192
|
}
|
|
164
|
-
|
|
193
|
+
|
|
165
194
|
private func setupActiveCallSubscription() {
|
|
166
195
|
if let streamVideo = streamVideo {
|
|
167
196
|
Task {
|
|
168
197
|
for await event in streamVideo.subscribe() {
|
|
169
198
|
// print("Event", event)
|
|
170
199
|
if let ringingEvent = event.rawValue as? CallRingEvent {
|
|
171
|
-
|
|
172
|
-
"callId": ringingEvent.callCid,
|
|
173
|
-
"state": "ringing"
|
|
174
|
-
])
|
|
200
|
+
updateCallStatusAndNotify(callId: ringingEvent.callCid, state: "ringing")
|
|
175
201
|
continue
|
|
176
202
|
}
|
|
177
|
-
|
|
203
|
+
|
|
178
204
|
if let callCreatedEvent = event.rawValue as? CallCreatedEvent {
|
|
179
205
|
print("CallCreatedEvent \(String(describing: userId))")
|
|
180
|
-
|
|
206
|
+
|
|
181
207
|
let callCid = callCreatedEvent.callCid
|
|
182
208
|
let members = callCreatedEvent.members
|
|
183
|
-
|
|
209
|
+
|
|
184
210
|
// Create timer on main thread
|
|
185
211
|
await MainActor.run {
|
|
186
212
|
// Store in the combined callStates map
|
|
@@ -190,20 +216,22 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
190
216
|
createdAt: Date(),
|
|
191
217
|
timer: nil
|
|
192
218
|
)
|
|
193
|
-
|
|
219
|
+
|
|
194
220
|
// Start timer to check for timeout every second
|
|
195
221
|
// Use @objc method as timer target to avoid sendable closure issues
|
|
196
222
|
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.checkCallTimeoutTimer(_:)), userInfo: callCid, repeats: true)
|
|
197
|
-
|
|
223
|
+
|
|
198
224
|
// Update timer in callStates
|
|
199
225
|
self.callStates[callCid]?.timer = timer
|
|
200
226
|
}
|
|
227
|
+
|
|
228
|
+
updateCallStatusAndNotify(callId: callCid, state: "created")
|
|
201
229
|
}
|
|
202
|
-
|
|
230
|
+
|
|
203
231
|
if let rejectedEvent = event.rawValue as? CallRejectedEvent {
|
|
204
232
|
let userId = rejectedEvent.user.id
|
|
205
233
|
let callCid = rejectedEvent.callCid
|
|
206
|
-
|
|
234
|
+
|
|
207
235
|
// Operate on callStates on the main thread
|
|
208
236
|
await MainActor.run {
|
|
209
237
|
// Update the combined callStates map
|
|
@@ -212,22 +240,18 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
212
240
|
self.callStates[callCid] = callState
|
|
213
241
|
}
|
|
214
242
|
}
|
|
215
|
-
|
|
243
|
+
|
|
216
244
|
print("CallRejectedEvent \(userId)")
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"state": "rejected",
|
|
220
|
-
"userId": userId
|
|
221
|
-
])
|
|
222
|
-
|
|
245
|
+
updateCallStatusAndNotify(callId: callCid, state: "rejected", userId: userId)
|
|
246
|
+
|
|
223
247
|
await checkAllParticipantsResponded(callCid: callCid)
|
|
224
248
|
continue
|
|
225
249
|
}
|
|
226
|
-
|
|
250
|
+
|
|
227
251
|
if let missedEvent = event.rawValue as? CallMissedEvent {
|
|
228
252
|
let userId = missedEvent.user.id
|
|
229
253
|
let callCid = missedEvent.callCid
|
|
230
|
-
|
|
254
|
+
|
|
231
255
|
// Operate on callStates on the main thread
|
|
232
256
|
await MainActor.run {
|
|
233
257
|
// Update the combined callStates map
|
|
@@ -236,73 +260,74 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
236
260
|
self.callStates[callCid] = callState
|
|
237
261
|
}
|
|
238
262
|
}
|
|
239
|
-
|
|
263
|
+
|
|
240
264
|
print("CallMissedEvent \(userId)")
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
"state": "missed",
|
|
244
|
-
"userId": userId
|
|
245
|
-
])
|
|
246
|
-
|
|
265
|
+
updateCallStatusAndNotify(callId: callCid, state: "missed", userId: userId)
|
|
266
|
+
|
|
247
267
|
await checkAllParticipantsResponded(callCid: callCid)
|
|
248
268
|
continue
|
|
249
269
|
}
|
|
250
|
-
|
|
270
|
+
|
|
251
271
|
if let participantLeftEvent = event.rawValue as? CallSessionParticipantLeftEvent {
|
|
252
272
|
let callIdSplit = participantLeftEvent.callCid.split(separator: ":")
|
|
253
|
-
if
|
|
273
|
+
if callIdSplit.count != 2 {
|
|
254
274
|
print("CallSessionParticipantLeftEvent invalid cID \(participantLeftEvent.callCid)")
|
|
255
275
|
continue
|
|
256
276
|
}
|
|
257
|
-
|
|
277
|
+
|
|
258
278
|
let callType = callIdSplit[0]
|
|
259
279
|
let callId = callIdSplit[1]
|
|
260
|
-
|
|
280
|
+
|
|
261
281
|
let call = streamVideo.call(callType: String(callType), callId: String(callId))
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
282
|
+
guard let participantsCount = await MainActor.run(body: {
|
|
283
|
+
if call.id == streamVideo.state.activeCall?.id {
|
|
284
|
+
return (call.state.session?.participants.count) ?? streamVideo.state.activeCall?.state.participants.count
|
|
285
|
+
} else {
|
|
286
|
+
return (call.state.session?.participants.count)
|
|
287
|
+
}
|
|
288
|
+
}) else {
|
|
289
|
+
print("CallSessionParticipantLeftEvent no participantsCount")
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if participantsCount - 1 <= 1 {
|
|
294
|
+
|
|
295
|
+
print("We are left solo in a call. Ending. cID: \(participantLeftEvent.callCid). participantsCount: \(participantsCount)")
|
|
296
|
+
|
|
267
297
|
Task {
|
|
268
298
|
if let activeCall = streamVideo.state.activeCall {
|
|
269
299
|
activeCall.leave()
|
|
300
|
+
} else {
|
|
301
|
+
print("Active call isn't the one?")
|
|
270
302
|
}
|
|
271
303
|
}
|
|
272
304
|
}
|
|
273
305
|
}
|
|
274
|
-
|
|
306
|
+
|
|
275
307
|
if let acceptedEvent = event.rawValue as? CallAcceptedEvent {
|
|
276
308
|
let userId = acceptedEvent.user.id
|
|
277
309
|
let callCid = acceptedEvent.callCid
|
|
278
|
-
|
|
310
|
+
|
|
279
311
|
// Operate on callStates on the main thread
|
|
280
312
|
await MainActor.run {
|
|
281
313
|
// Update the combined callStates map
|
|
282
314
|
if var callState = self.callStates[callCid] {
|
|
283
315
|
callState.participantResponses[userId] = "accepted"
|
|
284
|
-
|
|
316
|
+
|
|
285
317
|
// If someone accepted, invalidate the timer as we don't need to check anymore
|
|
286
318
|
callState.timer?.invalidate()
|
|
287
319
|
callState.timer = nil
|
|
288
|
-
|
|
320
|
+
|
|
289
321
|
self.callStates[callCid] = callState
|
|
290
322
|
}
|
|
291
323
|
}
|
|
292
|
-
|
|
324
|
+
|
|
293
325
|
print("CallAcceptedEvent \(userId)")
|
|
294
|
-
|
|
295
|
-
"callId": callCid,
|
|
296
|
-
"state": "accepted",
|
|
297
|
-
"userId": userId
|
|
298
|
-
])
|
|
326
|
+
updateCallStatusAndNotify(callId: callCid, state: "accepted", userId: userId)
|
|
299
327
|
continue
|
|
300
328
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"callId": streamVideo.state.activeCall?.callId ?? "",
|
|
304
|
-
"state": event.type
|
|
305
|
-
])
|
|
329
|
+
|
|
330
|
+
updateCallStatusAndNotify(callId: streamVideo.state.activeCall?.callId ?? "", state: event.type)
|
|
306
331
|
}
|
|
307
332
|
}
|
|
308
333
|
}
|
|
@@ -311,60 +336,54 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
311
336
|
// Create new subscription
|
|
312
337
|
activeCallSubscription = streamVideo?.state.$activeCall.sink { [weak self] newState in
|
|
313
338
|
guard let self = self else { return }
|
|
314
|
-
|
|
339
|
+
|
|
315
340
|
Task { @MainActor in
|
|
316
341
|
do {
|
|
317
342
|
try self.requireInitialized()
|
|
318
343
|
print("Call State Update:")
|
|
319
344
|
print("- Call is nil: \(newState == nil)")
|
|
320
|
-
|
|
345
|
+
|
|
321
346
|
if let state = newState?.state {
|
|
322
347
|
print("- state: \(state)")
|
|
323
348
|
print("- Session ID: \(state.sessionId)")
|
|
324
349
|
print("- All participants: \(String(describing: state.participants))")
|
|
325
350
|
print("- Remote participants: \(String(describing: state.remoteParticipants))")
|
|
326
|
-
|
|
351
|
+
|
|
327
352
|
// Store the active call ID when a call becomes active
|
|
328
353
|
self.currentActiveCallId = newState?.cId
|
|
329
354
|
print("Updated current active call ID: \(String(describing: self.currentActiveCallId))")
|
|
330
|
-
|
|
355
|
+
|
|
331
356
|
// Update overlay and make visible when there's an active call
|
|
332
357
|
self.overlayViewModel?.updateCall(newState)
|
|
333
358
|
self.overlayView?.isHidden = false
|
|
334
359
|
self.webView?.isOpaque = false
|
|
335
|
-
|
|
360
|
+
|
|
336
361
|
// Notify that a call has started
|
|
337
|
-
self.
|
|
338
|
-
"callId": newState?.cId ?? "",
|
|
339
|
-
"state": "joined"
|
|
340
|
-
])
|
|
362
|
+
self.updateCallStatusAndNotify(callId: newState?.cId ?? "", state: "joined")
|
|
341
363
|
} else {
|
|
342
364
|
// Get the call ID that was active before the state changed to nil
|
|
343
365
|
let endingCallId = self.currentActiveCallId
|
|
344
366
|
print("Call ending: \(String(describing: endingCallId))")
|
|
345
|
-
|
|
367
|
+
|
|
346
368
|
// If newState is nil, hide overlay and clear call
|
|
347
369
|
self.overlayViewModel?.updateCall(nil)
|
|
348
370
|
self.overlayView?.isHidden = true
|
|
349
371
|
self.webView?.isOpaque = true
|
|
350
|
-
|
|
372
|
+
|
|
351
373
|
// Notify that call has ended - use the properly tracked call ID
|
|
352
|
-
self.
|
|
353
|
-
|
|
354
|
-
"state": "left"
|
|
355
|
-
])
|
|
356
|
-
|
|
374
|
+
self.updateCallStatusAndNotify(callId: endingCallId ?? "", state: "left")
|
|
375
|
+
|
|
357
376
|
// Clean up any resources for this call
|
|
358
377
|
if let callCid = endingCallId {
|
|
359
378
|
// Invalidate and remove the timer
|
|
360
379
|
self.callStates[callCid]?.timer?.invalidate()
|
|
361
|
-
|
|
380
|
+
|
|
362
381
|
// Remove call from callStates
|
|
363
382
|
self.callStates.removeValue(forKey: callCid)
|
|
364
|
-
|
|
383
|
+
|
|
365
384
|
print("Cleaned up resources for ended call: \(callCid)")
|
|
366
385
|
}
|
|
367
|
-
|
|
386
|
+
|
|
368
387
|
// Clear the active call ID
|
|
369
388
|
self.currentActiveCallId = nil
|
|
370
389
|
}
|
|
@@ -374,45 +393,45 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
374
393
|
}
|
|
375
394
|
}
|
|
376
395
|
}
|
|
377
|
-
|
|
396
|
+
|
|
378
397
|
@objc private func checkCallTimeoutTimer(_ timer: Timer) {
|
|
379
398
|
guard let callCid = timer.userInfo as? String else { return }
|
|
380
|
-
|
|
399
|
+
|
|
381
400
|
Task { [weak self] in
|
|
382
401
|
guard let self = self else { return }
|
|
383
402
|
await self.checkCallTimeout(callCid: callCid)
|
|
384
403
|
}
|
|
385
404
|
}
|
|
386
|
-
|
|
405
|
+
|
|
387
406
|
private func checkCallTimeout(callCid: String) async {
|
|
388
407
|
// Get a local copy of the call state from the main thread
|
|
389
408
|
let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
|
|
390
409
|
return self.callStates[callCid]
|
|
391
410
|
}
|
|
392
|
-
|
|
411
|
+
|
|
393
412
|
guard let callState = callState else { return }
|
|
394
|
-
|
|
413
|
+
|
|
395
414
|
// Calculate time elapsed since call creation
|
|
396
415
|
let now = Date()
|
|
397
416
|
let elapsedSeconds = now.timeIntervalSince(callState.createdAt)
|
|
398
|
-
|
|
417
|
+
|
|
399
418
|
// Check if 30 seconds have passed
|
|
400
419
|
if elapsedSeconds >= 30.0 {
|
|
401
420
|
|
|
402
421
|
// Check if anyone has accepted
|
|
403
422
|
let hasAccepted = callState.participantResponses.values.contains { $0 == "accepted" }
|
|
404
|
-
|
|
423
|
+
|
|
405
424
|
if !hasAccepted {
|
|
406
425
|
print("Call \(callCid) has timed out after \(elapsedSeconds) seconds")
|
|
407
426
|
print("No one accepted call \(callCid), marking all non-responders as missed")
|
|
408
|
-
|
|
427
|
+
|
|
409
428
|
// Mark all members who haven't responded as "missed"
|
|
410
429
|
for member in callState.members {
|
|
411
430
|
let memberId = member.userId
|
|
412
431
|
let needsToBeMarkedAsMissed = await MainActor.run {
|
|
413
432
|
return self.callStates[callCid]?.participantResponses[memberId] == nil
|
|
414
433
|
}
|
|
415
|
-
|
|
434
|
+
|
|
416
435
|
if needsToBeMarkedAsMissed {
|
|
417
436
|
// Update callStates map on main thread
|
|
418
437
|
await MainActor.run {
|
|
@@ -422,23 +441,19 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
422
441
|
self.callStates[callCid] = updatedCallState
|
|
423
442
|
}
|
|
424
443
|
}
|
|
425
|
-
|
|
444
|
+
|
|
426
445
|
// Notify listeners
|
|
427
446
|
await MainActor.run {
|
|
428
|
-
self.
|
|
429
|
-
"callId": callCid,
|
|
430
|
-
"state": "missed",
|
|
431
|
-
"userId": memberId
|
|
432
|
-
])
|
|
447
|
+
self.updateCallStatusAndNotify(callId: callCid, state: "missed", userId: memberId)
|
|
433
448
|
}
|
|
434
449
|
}
|
|
435
450
|
}
|
|
436
|
-
|
|
451
|
+
|
|
437
452
|
// End the call
|
|
438
453
|
if let call = streamVideo?.state.activeCall, call.cId == callCid {
|
|
439
454
|
call.leave()
|
|
440
455
|
}
|
|
441
|
-
|
|
456
|
+
|
|
442
457
|
// Clean up timer on main thread
|
|
443
458
|
await MainActor.run {
|
|
444
459
|
self.callStates[callCid]?.timer?.invalidate()
|
|
@@ -447,74 +462,66 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
447
462
|
if let updatedCallState = updatedCallState {
|
|
448
463
|
self.callStates[callCid] = updatedCallState
|
|
449
464
|
}
|
|
450
|
-
|
|
465
|
+
|
|
451
466
|
// Remove from callStates
|
|
452
467
|
self.callStates.removeValue(forKey: callCid)
|
|
453
468
|
}
|
|
454
|
-
|
|
469
|
+
|
|
455
470
|
// Update UI
|
|
456
471
|
await MainActor.run {
|
|
457
472
|
self.overlayViewModel?.updateCall(nil)
|
|
458
473
|
self.overlayView?.isHidden = true
|
|
459
474
|
self.webView?.isOpaque = true
|
|
460
|
-
self.
|
|
461
|
-
"callId": callCid,
|
|
462
|
-
"state": "ended",
|
|
463
|
-
"reason": "timeout"
|
|
464
|
-
])
|
|
475
|
+
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "timeout")
|
|
465
476
|
}
|
|
466
477
|
}
|
|
467
478
|
}
|
|
468
479
|
}
|
|
469
|
-
|
|
480
|
+
|
|
470
481
|
private func checkAllParticipantsResponded(callCid: String) async {
|
|
471
482
|
// Get a local copy of the call state from the main thread
|
|
472
483
|
let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
|
|
473
484
|
return self.callStates[callCid]
|
|
474
485
|
}
|
|
475
|
-
|
|
486
|
+
|
|
476
487
|
guard let callState = callState else {
|
|
477
488
|
print("Call state not found for cId: \(callCid)")
|
|
478
489
|
return
|
|
479
490
|
}
|
|
480
|
-
|
|
491
|
+
|
|
481
492
|
let totalParticipants = callState.members.count
|
|
482
493
|
let responseCount = callState.participantResponses.count
|
|
483
|
-
|
|
494
|
+
|
|
484
495
|
print("Total participants: \(totalParticipants), Responses: \(responseCount)")
|
|
485
|
-
|
|
496
|
+
|
|
486
497
|
let allResponded = responseCount >= totalParticipants
|
|
487
|
-
let allRejectedOrMissed = allResponded &&
|
|
498
|
+
let allRejectedOrMissed = allResponded &&
|
|
488
499
|
callState.participantResponses.values.allSatisfy { $0 == "rejected" || $0 == "missed" }
|
|
489
|
-
|
|
500
|
+
|
|
490
501
|
if allResponded && allRejectedOrMissed {
|
|
491
502
|
print("All participants have rejected or missed the call")
|
|
492
|
-
|
|
493
|
-
// End the
|
|
503
|
+
|
|
504
|
+
// End the call
|
|
494
505
|
if let call = streamVideo?.state.activeCall, call.cId == callCid {
|
|
495
506
|
call.leave()
|
|
496
507
|
}
|
|
497
|
-
|
|
508
|
+
|
|
498
509
|
// Clean up timer and remove from callStates on main thread
|
|
499
510
|
await MainActor.run {
|
|
500
511
|
// Clean up timer
|
|
501
512
|
self.callStates[callCid]?.timer?.invalidate()
|
|
502
|
-
|
|
513
|
+
|
|
503
514
|
// Remove from callStates
|
|
504
515
|
self.callStates.removeValue(forKey: callCid)
|
|
505
|
-
|
|
516
|
+
|
|
506
517
|
self.overlayViewModel?.updateCall(nil)
|
|
507
518
|
self.overlayView?.isHidden = true
|
|
508
519
|
self.webView?.isOpaque = true
|
|
509
|
-
self.
|
|
510
|
-
"callId": callCid,
|
|
511
|
-
"state": "ended",
|
|
512
|
-
"reason": "all_rejected_or_missed"
|
|
513
|
-
])
|
|
520
|
+
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "all_rejected_or_missed")
|
|
514
521
|
}
|
|
515
522
|
}
|
|
516
523
|
}
|
|
517
|
-
|
|
524
|
+
|
|
518
525
|
@objc func login(_ call: CAPPluginCall) {
|
|
519
526
|
guard let token = call.getString("token"),
|
|
520
527
|
let userId = call.getString("userId"),
|
|
@@ -522,7 +529,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
522
529
|
call.reject("Missing required parameters")
|
|
523
530
|
return
|
|
524
531
|
}
|
|
525
|
-
|
|
532
|
+
|
|
526
533
|
let imageURL = call.getString("imageURL")
|
|
527
534
|
let user = User(
|
|
528
535
|
id: userId,
|
|
@@ -530,31 +537,31 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
530
537
|
imageURL: imageURL.flatMap { URL(string: $0) },
|
|
531
538
|
customData: [:]
|
|
532
539
|
)
|
|
533
|
-
|
|
540
|
+
|
|
534
541
|
let credentials = UserCredentials(user: user, tokenValue: token)
|
|
535
542
|
SecureUserRepository.shared.save(user: credentials)
|
|
536
543
|
// Initialize Stream Video with new credentials
|
|
537
544
|
initializeStreamVideo()
|
|
538
|
-
|
|
545
|
+
|
|
539
546
|
if state != .initialized {
|
|
540
547
|
call.reject("Failed to initialize StreamVideo")
|
|
541
548
|
return
|
|
542
549
|
}
|
|
543
|
-
|
|
550
|
+
|
|
544
551
|
// Update the CallOverlayView with new StreamVideo instance
|
|
545
552
|
Task { @MainActor in
|
|
546
553
|
self.overlayViewModel?.updateStreamVideo(self.streamVideo)
|
|
547
554
|
}
|
|
548
|
-
|
|
555
|
+
|
|
549
556
|
call.resolve([
|
|
550
557
|
"success": true
|
|
551
558
|
])
|
|
552
559
|
}
|
|
553
|
-
|
|
560
|
+
|
|
554
561
|
@objc func logout(_ call: CAPPluginCall) {
|
|
555
562
|
// Remove VOIP token from repository
|
|
556
563
|
SecureUserRepository.shared.save(voipPushToken: nil)
|
|
557
|
-
|
|
564
|
+
|
|
558
565
|
// Try to delete the device from Stream if we have the last token
|
|
559
566
|
if let lastVoIPToken = lastVoIPToken {
|
|
560
567
|
Task {
|
|
@@ -565,16 +572,16 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
565
572
|
}
|
|
566
573
|
}
|
|
567
574
|
}
|
|
568
|
-
|
|
575
|
+
|
|
569
576
|
// Cancel subscriptions
|
|
570
577
|
tokenSubscription?.cancel()
|
|
571
578
|
tokenSubscription = nil
|
|
572
579
|
activeCallSubscription?.cancel()
|
|
573
580
|
activeCallSubscription = nil
|
|
574
581
|
lastVoIPToken = nil
|
|
575
|
-
|
|
582
|
+
|
|
576
583
|
SecureUserRepository.shared.removeCurrentUser()
|
|
577
|
-
|
|
584
|
+
|
|
578
585
|
// Update the CallOverlayView with nil StreamVideo instance
|
|
579
586
|
Task { @MainActor in
|
|
580
587
|
self.overlayViewModel?.updateCall(nil)
|
|
@@ -582,19 +589,19 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
582
589
|
self.overlayView?.isHidden = true
|
|
583
590
|
self.webView?.isOpaque = true
|
|
584
591
|
}
|
|
585
|
-
|
|
592
|
+
|
|
586
593
|
call.resolve([
|
|
587
594
|
"success": true
|
|
588
595
|
])
|
|
589
596
|
}
|
|
590
|
-
|
|
597
|
+
|
|
591
598
|
@objc func isCameraEnabled(_ call: CAPPluginCall) {
|
|
592
599
|
do {
|
|
593
600
|
try requireInitialized()
|
|
594
601
|
} catch {
|
|
595
602
|
call.reject("SDK not initialized")
|
|
596
603
|
}
|
|
597
|
-
|
|
604
|
+
|
|
598
605
|
if let activeCall = streamVideo?.state.activeCall {
|
|
599
606
|
call.resolve([
|
|
600
607
|
"enabled": activeCall.camera.status == .enabled
|
|
@@ -629,6 +636,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
629
636
|
|
|
630
637
|
let callType = call.getString("type") ?? "default"
|
|
631
638
|
let shouldRing = call.getBool("ring") ?? true
|
|
639
|
+
let team = call.getString("team")
|
|
632
640
|
|
|
633
641
|
// Generate a unique call ID
|
|
634
642
|
let callId = UUID().uuidString
|
|
@@ -640,6 +648,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
640
648
|
print("- Call Type: \(callType)")
|
|
641
649
|
print("- Users: \(members)")
|
|
642
650
|
print("- Should Ring: \(shouldRing)")
|
|
651
|
+
print("- Team: \(team)")
|
|
643
652
|
|
|
644
653
|
// Create the call object
|
|
645
654
|
let streamCall = streamVideo?.call(callType: callType, callId: callId)
|
|
@@ -649,7 +658,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
649
658
|
try await streamCall?.create(
|
|
650
659
|
memberIds: members,
|
|
651
660
|
custom: [:],
|
|
652
|
-
ring: shouldRing
|
|
661
|
+
team: team, ring: shouldRing
|
|
653
662
|
)
|
|
654
663
|
|
|
655
664
|
// Join the call
|
|
@@ -788,6 +797,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
788
797
|
print("Accepting and joining call \(streamCall!.cId)...")
|
|
789
798
|
try await streamCall?.accept()
|
|
790
799
|
try await streamCall?.join(create: false)
|
|
800
|
+
try await streamCall?.get()
|
|
791
801
|
print("Successfully joined call")
|
|
792
802
|
|
|
793
803
|
// Update the CallOverlayView with the active call
|
|
@@ -900,4 +910,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
900
910
|
])
|
|
901
911
|
}
|
|
902
912
|
}
|
|
913
|
+
|
|
914
|
+
@objc func getCallStatus(_ call: CAPPluginCall) {
|
|
915
|
+
// Use stored state rather than accessing SDK state directly
|
|
916
|
+
if currentCallId.isEmpty || currentCallState == "left" {
|
|
917
|
+
call.reject("Not in a call")
|
|
918
|
+
return
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
call.resolve([
|
|
922
|
+
"callId": currentCallId,
|
|
923
|
+
"state": currentCallState
|
|
924
|
+
])
|
|
925
|
+
}
|
|
903
926
|
}
|