@capgo/capacitor-stream-call 0.0.6 → 0.0.18
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 +138 -201
- 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 +151 -142
- 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,34 +260,29 @@ 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
282
|
if await MainActor.run(body: { (call.state.session?.participants.count ?? 1) - 1 <= 1 }) {
|
|
263
|
-
|
|
264
|
-
|
|
283
|
+
|
|
265
284
|
print("We are left solo in a call. Ending. cID: \(participantLeftEvent.callCid)")
|
|
266
|
-
|
|
285
|
+
|
|
267
286
|
Task {
|
|
268
287
|
if let activeCall = streamVideo.state.activeCall {
|
|
269
288
|
activeCall.leave()
|
|
@@ -271,38 +290,31 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
271
290
|
}
|
|
272
291
|
}
|
|
273
292
|
}
|
|
274
|
-
|
|
293
|
+
|
|
275
294
|
if let acceptedEvent = event.rawValue as? CallAcceptedEvent {
|
|
276
295
|
let userId = acceptedEvent.user.id
|
|
277
296
|
let callCid = acceptedEvent.callCid
|
|
278
|
-
|
|
297
|
+
|
|
279
298
|
// Operate on callStates on the main thread
|
|
280
299
|
await MainActor.run {
|
|
281
300
|
// Update the combined callStates map
|
|
282
301
|
if var callState = self.callStates[callCid] {
|
|
283
302
|
callState.participantResponses[userId] = "accepted"
|
|
284
|
-
|
|
303
|
+
|
|
285
304
|
// If someone accepted, invalidate the timer as we don't need to check anymore
|
|
286
305
|
callState.timer?.invalidate()
|
|
287
306
|
callState.timer = nil
|
|
288
|
-
|
|
307
|
+
|
|
289
308
|
self.callStates[callCid] = callState
|
|
290
309
|
}
|
|
291
310
|
}
|
|
292
|
-
|
|
311
|
+
|
|
293
312
|
print("CallAcceptedEvent \(userId)")
|
|
294
|
-
|
|
295
|
-
"callId": callCid,
|
|
296
|
-
"state": "accepted",
|
|
297
|
-
"userId": userId
|
|
298
|
-
])
|
|
313
|
+
updateCallStatusAndNotify(callId: callCid, state: "accepted", userId: userId)
|
|
299
314
|
continue
|
|
300
315
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"callId": streamVideo.state.activeCall?.callId ?? "",
|
|
304
|
-
"state": event.type
|
|
305
|
-
])
|
|
316
|
+
|
|
317
|
+
updateCallStatusAndNotify(callId: streamVideo.state.activeCall?.callId ?? "", state: event.type)
|
|
306
318
|
}
|
|
307
319
|
}
|
|
308
320
|
}
|
|
@@ -311,60 +323,54 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
311
323
|
// Create new subscription
|
|
312
324
|
activeCallSubscription = streamVideo?.state.$activeCall.sink { [weak self] newState in
|
|
313
325
|
guard let self = self else { return }
|
|
314
|
-
|
|
326
|
+
|
|
315
327
|
Task { @MainActor in
|
|
316
328
|
do {
|
|
317
329
|
try self.requireInitialized()
|
|
318
330
|
print("Call State Update:")
|
|
319
331
|
print("- Call is nil: \(newState == nil)")
|
|
320
|
-
|
|
332
|
+
|
|
321
333
|
if let state = newState?.state {
|
|
322
334
|
print("- state: \(state)")
|
|
323
335
|
print("- Session ID: \(state.sessionId)")
|
|
324
336
|
print("- All participants: \(String(describing: state.participants))")
|
|
325
337
|
print("- Remote participants: \(String(describing: state.remoteParticipants))")
|
|
326
|
-
|
|
338
|
+
|
|
327
339
|
// Store the active call ID when a call becomes active
|
|
328
340
|
self.currentActiveCallId = newState?.cId
|
|
329
341
|
print("Updated current active call ID: \(String(describing: self.currentActiveCallId))")
|
|
330
|
-
|
|
342
|
+
|
|
331
343
|
// Update overlay and make visible when there's an active call
|
|
332
344
|
self.overlayViewModel?.updateCall(newState)
|
|
333
345
|
self.overlayView?.isHidden = false
|
|
334
346
|
self.webView?.isOpaque = false
|
|
335
|
-
|
|
347
|
+
|
|
336
348
|
// Notify that a call has started
|
|
337
|
-
self.
|
|
338
|
-
"callId": newState?.cId ?? "",
|
|
339
|
-
"state": "joined"
|
|
340
|
-
])
|
|
349
|
+
self.updateCallStatusAndNotify(callId: newState?.cId ?? "", state: "joined")
|
|
341
350
|
} else {
|
|
342
351
|
// Get the call ID that was active before the state changed to nil
|
|
343
352
|
let endingCallId = self.currentActiveCallId
|
|
344
353
|
print("Call ending: \(String(describing: endingCallId))")
|
|
345
|
-
|
|
354
|
+
|
|
346
355
|
// If newState is nil, hide overlay and clear call
|
|
347
356
|
self.overlayViewModel?.updateCall(nil)
|
|
348
357
|
self.overlayView?.isHidden = true
|
|
349
358
|
self.webView?.isOpaque = true
|
|
350
|
-
|
|
359
|
+
|
|
351
360
|
// Notify that call has ended - use the properly tracked call ID
|
|
352
|
-
self.
|
|
353
|
-
|
|
354
|
-
"state": "left"
|
|
355
|
-
])
|
|
356
|
-
|
|
361
|
+
self.updateCallStatusAndNotify(callId: endingCallId ?? "", state: "left")
|
|
362
|
+
|
|
357
363
|
// Clean up any resources for this call
|
|
358
364
|
if let callCid = endingCallId {
|
|
359
365
|
// Invalidate and remove the timer
|
|
360
366
|
self.callStates[callCid]?.timer?.invalidate()
|
|
361
|
-
|
|
367
|
+
|
|
362
368
|
// Remove call from callStates
|
|
363
369
|
self.callStates.removeValue(forKey: callCid)
|
|
364
|
-
|
|
370
|
+
|
|
365
371
|
print("Cleaned up resources for ended call: \(callCid)")
|
|
366
372
|
}
|
|
367
|
-
|
|
373
|
+
|
|
368
374
|
// Clear the active call ID
|
|
369
375
|
self.currentActiveCallId = nil
|
|
370
376
|
}
|
|
@@ -374,45 +380,45 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
382
|
}
|
|
377
|
-
|
|
383
|
+
|
|
378
384
|
@objc private func checkCallTimeoutTimer(_ timer: Timer) {
|
|
379
385
|
guard let callCid = timer.userInfo as? String else { return }
|
|
380
|
-
|
|
386
|
+
|
|
381
387
|
Task { [weak self] in
|
|
382
388
|
guard let self = self else { return }
|
|
383
389
|
await self.checkCallTimeout(callCid: callCid)
|
|
384
390
|
}
|
|
385
391
|
}
|
|
386
|
-
|
|
392
|
+
|
|
387
393
|
private func checkCallTimeout(callCid: String) async {
|
|
388
394
|
// Get a local copy of the call state from the main thread
|
|
389
395
|
let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
|
|
390
396
|
return self.callStates[callCid]
|
|
391
397
|
}
|
|
392
|
-
|
|
398
|
+
|
|
393
399
|
guard let callState = callState else { return }
|
|
394
|
-
|
|
400
|
+
|
|
395
401
|
// Calculate time elapsed since call creation
|
|
396
402
|
let now = Date()
|
|
397
403
|
let elapsedSeconds = now.timeIntervalSince(callState.createdAt)
|
|
398
|
-
|
|
404
|
+
|
|
399
405
|
// Check if 30 seconds have passed
|
|
400
406
|
if elapsedSeconds >= 30.0 {
|
|
401
407
|
|
|
402
408
|
// Check if anyone has accepted
|
|
403
409
|
let hasAccepted = callState.participantResponses.values.contains { $0 == "accepted" }
|
|
404
|
-
|
|
410
|
+
|
|
405
411
|
if !hasAccepted {
|
|
406
412
|
print("Call \(callCid) has timed out after \(elapsedSeconds) seconds")
|
|
407
413
|
print("No one accepted call \(callCid), marking all non-responders as missed")
|
|
408
|
-
|
|
414
|
+
|
|
409
415
|
// Mark all members who haven't responded as "missed"
|
|
410
416
|
for member in callState.members {
|
|
411
417
|
let memberId = member.userId
|
|
412
418
|
let needsToBeMarkedAsMissed = await MainActor.run {
|
|
413
419
|
return self.callStates[callCid]?.participantResponses[memberId] == nil
|
|
414
420
|
}
|
|
415
|
-
|
|
421
|
+
|
|
416
422
|
if needsToBeMarkedAsMissed {
|
|
417
423
|
// Update callStates map on main thread
|
|
418
424
|
await MainActor.run {
|
|
@@ -422,23 +428,19 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
422
428
|
self.callStates[callCid] = updatedCallState
|
|
423
429
|
}
|
|
424
430
|
}
|
|
425
|
-
|
|
431
|
+
|
|
426
432
|
// Notify listeners
|
|
427
433
|
await MainActor.run {
|
|
428
|
-
self.
|
|
429
|
-
"callId": callCid,
|
|
430
|
-
"state": "missed",
|
|
431
|
-
"userId": memberId
|
|
432
|
-
])
|
|
434
|
+
self.updateCallStatusAndNotify(callId: callCid, state: "missed", userId: memberId)
|
|
433
435
|
}
|
|
434
436
|
}
|
|
435
437
|
}
|
|
436
|
-
|
|
438
|
+
|
|
437
439
|
// End the call
|
|
438
440
|
if let call = streamVideo?.state.activeCall, call.cId == callCid {
|
|
439
441
|
call.leave()
|
|
440
442
|
}
|
|
441
|
-
|
|
443
|
+
|
|
442
444
|
// Clean up timer on main thread
|
|
443
445
|
await MainActor.run {
|
|
444
446
|
self.callStates[callCid]?.timer?.invalidate()
|
|
@@ -447,74 +449,66 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
447
449
|
if let updatedCallState = updatedCallState {
|
|
448
450
|
self.callStates[callCid] = updatedCallState
|
|
449
451
|
}
|
|
450
|
-
|
|
452
|
+
|
|
451
453
|
// Remove from callStates
|
|
452
454
|
self.callStates.removeValue(forKey: callCid)
|
|
453
455
|
}
|
|
454
|
-
|
|
456
|
+
|
|
455
457
|
// Update UI
|
|
456
458
|
await MainActor.run {
|
|
457
459
|
self.overlayViewModel?.updateCall(nil)
|
|
458
460
|
self.overlayView?.isHidden = true
|
|
459
461
|
self.webView?.isOpaque = true
|
|
460
|
-
self.
|
|
461
|
-
"callId": callCid,
|
|
462
|
-
"state": "ended",
|
|
463
|
-
"reason": "timeout"
|
|
464
|
-
])
|
|
462
|
+
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "timeout")
|
|
465
463
|
}
|
|
466
464
|
}
|
|
467
465
|
}
|
|
468
466
|
}
|
|
469
|
-
|
|
467
|
+
|
|
470
468
|
private func checkAllParticipantsResponded(callCid: String) async {
|
|
471
469
|
// Get a local copy of the call state from the main thread
|
|
472
470
|
let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
|
|
473
471
|
return self.callStates[callCid]
|
|
474
472
|
}
|
|
475
|
-
|
|
473
|
+
|
|
476
474
|
guard let callState = callState else {
|
|
477
475
|
print("Call state not found for cId: \(callCid)")
|
|
478
476
|
return
|
|
479
477
|
}
|
|
480
|
-
|
|
478
|
+
|
|
481
479
|
let totalParticipants = callState.members.count
|
|
482
480
|
let responseCount = callState.participantResponses.count
|
|
483
|
-
|
|
481
|
+
|
|
484
482
|
print("Total participants: \(totalParticipants), Responses: \(responseCount)")
|
|
485
|
-
|
|
483
|
+
|
|
486
484
|
let allResponded = responseCount >= totalParticipants
|
|
487
|
-
let allRejectedOrMissed = allResponded &&
|
|
485
|
+
let allRejectedOrMissed = allResponded &&
|
|
488
486
|
callState.participantResponses.values.allSatisfy { $0 == "rejected" || $0 == "missed" }
|
|
489
|
-
|
|
487
|
+
|
|
490
488
|
if allResponded && allRejectedOrMissed {
|
|
491
489
|
print("All participants have rejected or missed the call")
|
|
492
|
-
|
|
493
|
-
// End the
|
|
490
|
+
|
|
491
|
+
// End the call
|
|
494
492
|
if let call = streamVideo?.state.activeCall, call.cId == callCid {
|
|
495
493
|
call.leave()
|
|
496
494
|
}
|
|
497
|
-
|
|
495
|
+
|
|
498
496
|
// Clean up timer and remove from callStates on main thread
|
|
499
497
|
await MainActor.run {
|
|
500
498
|
// Clean up timer
|
|
501
499
|
self.callStates[callCid]?.timer?.invalidate()
|
|
502
|
-
|
|
500
|
+
|
|
503
501
|
// Remove from callStates
|
|
504
502
|
self.callStates.removeValue(forKey: callCid)
|
|
505
|
-
|
|
503
|
+
|
|
506
504
|
self.overlayViewModel?.updateCall(nil)
|
|
507
505
|
self.overlayView?.isHidden = true
|
|
508
506
|
self.webView?.isOpaque = true
|
|
509
|
-
self.
|
|
510
|
-
"callId": callCid,
|
|
511
|
-
"state": "ended",
|
|
512
|
-
"reason": "all_rejected_or_missed"
|
|
513
|
-
])
|
|
507
|
+
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "all_rejected_or_missed")
|
|
514
508
|
}
|
|
515
509
|
}
|
|
516
510
|
}
|
|
517
|
-
|
|
511
|
+
|
|
518
512
|
@objc func login(_ call: CAPPluginCall) {
|
|
519
513
|
guard let token = call.getString("token"),
|
|
520
514
|
let userId = call.getString("userId"),
|
|
@@ -522,7 +516,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
522
516
|
call.reject("Missing required parameters")
|
|
523
517
|
return
|
|
524
518
|
}
|
|
525
|
-
|
|
519
|
+
|
|
526
520
|
let imageURL = call.getString("imageURL")
|
|
527
521
|
let user = User(
|
|
528
522
|
id: userId,
|
|
@@ -530,31 +524,31 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
530
524
|
imageURL: imageURL.flatMap { URL(string: $0) },
|
|
531
525
|
customData: [:]
|
|
532
526
|
)
|
|
533
|
-
|
|
527
|
+
|
|
534
528
|
let credentials = UserCredentials(user: user, tokenValue: token)
|
|
535
529
|
SecureUserRepository.shared.save(user: credentials)
|
|
536
530
|
// Initialize Stream Video with new credentials
|
|
537
531
|
initializeStreamVideo()
|
|
538
|
-
|
|
532
|
+
|
|
539
533
|
if state != .initialized {
|
|
540
534
|
call.reject("Failed to initialize StreamVideo")
|
|
541
535
|
return
|
|
542
536
|
}
|
|
543
|
-
|
|
537
|
+
|
|
544
538
|
// Update the CallOverlayView with new StreamVideo instance
|
|
545
539
|
Task { @MainActor in
|
|
546
540
|
self.overlayViewModel?.updateStreamVideo(self.streamVideo)
|
|
547
541
|
}
|
|
548
|
-
|
|
542
|
+
|
|
549
543
|
call.resolve([
|
|
550
544
|
"success": true
|
|
551
545
|
])
|
|
552
546
|
}
|
|
553
|
-
|
|
547
|
+
|
|
554
548
|
@objc func logout(_ call: CAPPluginCall) {
|
|
555
549
|
// Remove VOIP token from repository
|
|
556
550
|
SecureUserRepository.shared.save(voipPushToken: nil)
|
|
557
|
-
|
|
551
|
+
|
|
558
552
|
// Try to delete the device from Stream if we have the last token
|
|
559
553
|
if let lastVoIPToken = lastVoIPToken {
|
|
560
554
|
Task {
|
|
@@ -565,16 +559,16 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
565
559
|
}
|
|
566
560
|
}
|
|
567
561
|
}
|
|
568
|
-
|
|
562
|
+
|
|
569
563
|
// Cancel subscriptions
|
|
570
564
|
tokenSubscription?.cancel()
|
|
571
565
|
tokenSubscription = nil
|
|
572
566
|
activeCallSubscription?.cancel()
|
|
573
567
|
activeCallSubscription = nil
|
|
574
568
|
lastVoIPToken = nil
|
|
575
|
-
|
|
569
|
+
|
|
576
570
|
SecureUserRepository.shared.removeCurrentUser()
|
|
577
|
-
|
|
571
|
+
|
|
578
572
|
// Update the CallOverlayView with nil StreamVideo instance
|
|
579
573
|
Task { @MainActor in
|
|
580
574
|
self.overlayViewModel?.updateCall(nil)
|
|
@@ -582,19 +576,19 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
582
576
|
self.overlayView?.isHidden = true
|
|
583
577
|
self.webView?.isOpaque = true
|
|
584
578
|
}
|
|
585
|
-
|
|
579
|
+
|
|
586
580
|
call.resolve([
|
|
587
581
|
"success": true
|
|
588
582
|
])
|
|
589
583
|
}
|
|
590
|
-
|
|
584
|
+
|
|
591
585
|
@objc func isCameraEnabled(_ call: CAPPluginCall) {
|
|
592
586
|
do {
|
|
593
587
|
try requireInitialized()
|
|
594
588
|
} catch {
|
|
595
589
|
call.reject("SDK not initialized")
|
|
596
590
|
}
|
|
597
|
-
|
|
591
|
+
|
|
598
592
|
if let activeCall = streamVideo?.state.activeCall {
|
|
599
593
|
call.resolve([
|
|
600
594
|
"enabled": activeCall.camera.status == .enabled
|
|
@@ -629,6 +623,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
629
623
|
|
|
630
624
|
let callType = call.getString("type") ?? "default"
|
|
631
625
|
let shouldRing = call.getBool("ring") ?? true
|
|
626
|
+
let team = call.getString("team")
|
|
632
627
|
|
|
633
628
|
// Generate a unique call ID
|
|
634
629
|
let callId = UUID().uuidString
|
|
@@ -640,6 +635,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
640
635
|
print("- Call Type: \(callType)")
|
|
641
636
|
print("- Users: \(members)")
|
|
642
637
|
print("- Should Ring: \(shouldRing)")
|
|
638
|
+
print("- Team: \(team)")
|
|
643
639
|
|
|
644
640
|
// Create the call object
|
|
645
641
|
let streamCall = streamVideo?.call(callType: callType, callId: callId)
|
|
@@ -649,7 +645,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
649
645
|
try await streamCall?.create(
|
|
650
646
|
memberIds: members,
|
|
651
647
|
custom: [:],
|
|
652
|
-
ring: shouldRing
|
|
648
|
+
team: team, ring: shouldRing
|
|
653
649
|
)
|
|
654
650
|
|
|
655
651
|
// Join the call
|
|
@@ -900,4 +896,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
900
896
|
])
|
|
901
897
|
}
|
|
902
898
|
}
|
|
899
|
+
|
|
900
|
+
@objc func getCallStatus(_ call: CAPPluginCall) {
|
|
901
|
+
// Use stored state rather than accessing SDK state directly
|
|
902
|
+
if currentCallId.isEmpty || currentCallState == "left" {
|
|
903
|
+
call.reject("Not in a call")
|
|
904
|
+
return
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
call.resolve([
|
|
908
|
+
"callId": currentCallId,
|
|
909
|
+
"state": currentCallState
|
|
910
|
+
])
|
|
911
|
+
}
|
|
903
912
|
}
|