Skip to content

Commit d834172

Browse files
authored
[Fix]CallKitService ending calls wrongly in 1:1 (#850)
1 parent 739580a commit d834172

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

55
# Upcoming
66

7-
### 🔄 Changed
7+
### 🐞 Fixed
8+
- CallKit ending 1:1 calls prematurely. [#850](https://github.com/GetStream/stream-video-swift/pull/850)
89

910
# [1.25.0](https://github.com/GetStream/stream-video-swift/releases/tag/1.25.0)
1011
_June 16, 2025_

Sources/StreamVideo/CallKit/CallKitService.swift

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
2121
var createdBy: User?
2222
var isActive: Bool = false
2323
var ringingTimedOut: Bool = false
24+
var isEndedElsewhere: Bool = false
2425

2526
init(
2627
call: Call,
@@ -215,12 +216,24 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
215216
/// The call was accepted somewhere else (e.g the incoming call on the same device or another
216217
/// device). No action is required.
217218
guard
218-
response.user.id == streamVideo?.user.id,
219219
let newCallEntry = callEntry(for: response.callCid),
220-
newCallEntry.callUUID != active // Ensure that the new call isn't the currently active one.
220+
newCallEntry.callUUID != active, // Ensure that the new call isn't the currently active one.
221+
response.user.id == streamVideo?.user.id
221222
else {
222223
return
223224
}
225+
log.debug(
226+
"""
227+
Call rejected
228+
callId:\(newCallEntry.call.callId)
229+
callType:\(newCallEntry.call.callType)
230+
callerId:\(newCallEntry.createdBy?.id)
231+
ringingTimedOut:\(newCallEntry.ringingTimedOut)
232+
isEndedElsewhere:\(newCallEntry.isEndedElsewhere)
233+
""",
234+
subsystems: .callKit
235+
)
236+
224237
callProvider.reportCall(
225238
with: newCallEntry.callUUID,
226239
endedAt: nil,
@@ -252,6 +265,20 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
252265
else {
253266
return
254267
}
268+
log.debug(
269+
"""
270+
Call rejected
271+
callId:\(newCallEntry.call.callId)
272+
callType:\(newCallEntry.call.callType)
273+
callerId:\(newCallEntry.createdBy?.id)
274+
ringingTimedOut:\(newCallEntry.ringingTimedOut)
275+
isEndedElsewhere:\(newCallEntry.isEndedElsewhere)
276+
isCurrentUserRejection:\(isCurrentUserRejection)
277+
isCallCreatorRejection:\(isCallCreatorRejection)
278+
""",
279+
subsystems: .callKit
280+
)
281+
255282
callProvider.reportCall(
256283
with: newCallEntry.callUUID,
257284
endedAt: nil,
@@ -271,8 +298,24 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
271298
guard let callEndedEntry = callEntry(for: cId) else {
272299
return
273300
}
274-
callEndedEntry.ringingTimedOut = ringingTimedOut
301+
if ringingTimedOut {
302+
callEndedEntry.ringingTimedOut = ringingTimedOut
303+
} else {
304+
callEndedEntry.isEndedElsewhere = true
305+
}
275306
set(callEndedEntry, for: callEndedEntry.callUUID)
307+
308+
log.debug(
309+
"""
310+
CallEnded
311+
callId:\(callEndedEntry.call.callId)
312+
callType:\(callEndedEntry.call.callType)
313+
callerId:\(callEndedEntry.createdBy?.id)
314+
ringingTimedOut:\(callEndedEntry.ringingTimedOut)
315+
isEndedElsewhere:\(callEndedEntry.isEndedElsewhere)
316+
""",
317+
subsystems: .callKit
318+
)
276319
Task {
277320
do {
278321
// End the call.
@@ -298,6 +341,10 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
298341
Task { @MainActor in
299342
if let call = callEntry(for: response.callCid)?.call,
300343
call.state.participants.count == 1 {
344+
log.debug(
345+
"Call will end as only one participant left in the call",
346+
subsystems: .callKit
347+
)
301348
callEnded(response.callCid, ringingTimedOut: false)
302349
}
303350
}
@@ -527,12 +574,21 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
527574
return false
528575
}
529576

577+
var allMembers = callState.members.map(\.user.toUser)
578+
let creator = callState.call.createdBy.toUser
579+
let isUserInMembersArray = allMembers.filter { $0.id == creator.id }.isEmpty == false
580+
if !isUserInMembersArray {
581+
allMembers.append(creator)
582+
}
583+
let allCallees = allMembers.filter { $0.id != creator.id }
584+
530585
let currentUserId = streamVideo.user.id
531586
let acceptedBy = callState.call.session?.acceptedBy ?? [:]
532587
let rejectedBy = callState.call.session?.rejectedBy ?? [:]
533588
let isAccepted = acceptedBy[currentUserId] != nil
534589
let isRejected = rejectedBy[currentUserId] != nil
535-
let isRejectedByEveryoneElse = rejectedBy.keys.filter { $0 != currentUserId }.count == (callState.members.count - 1)
590+
let isRejectedByEveryoneElse = (allCallees.endIndex > 1)
591+
&& rejectedBy.keys.filter { $0 != currentUserId }.count == (allCallees.endIndex - 1)
536592
return isAccepted || isRejected || isRejectedByEveryoneElse
537593
}
538594

StreamVideoTests/CallKit/CallKitServiceTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,31 @@ final class CallKitServiceTests: XCTestCase, @unchecked Sendable {
201201
try await assertCallWasHandled(wasRejectedByEveryoneElse: true)
202202
}
203203

204+
func test_reportIncomingCall_streamVideoReconnectsCallerDidNotReject_callWasNotEnded() async throws {
205+
stubConnectionState(to: .disconnected(error: nil))
206+
await stubCall(
207+
response: .dummy(
208+
call: .dummy(
209+
session: .dummy(
210+
acceptedBy: [:],
211+
rejectedBy: [:]
212+
)
213+
),
214+
members: [.dummy(userId: user.id)]
215+
)
216+
)
217+
subject.streamVideo = mockedStreamVideo
218+
219+
try await assertNotRequestTransaction(CXEndCallAction.self) {
220+
subject.reportIncomingCall(
221+
cid,
222+
localizedCallerName: localizedCallerName,
223+
callerId: callerId,
224+
hasVideo: false
225+
) { _ in }
226+
}
227+
}
228+
204229
func test_reportIncomingCall_streamVideoConnectedAndCallIsAccepted_callWasEnded() async throws {
205230
stubConnectionState(to: .connected)
206231

0 commit comments

Comments
 (0)