mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Fix a couple of race conditions when observing room info updates for calls. (#3487)
* Fix a race condition observing room info updates for calls. * Fix a bug where call observation wasn't set up if the call comes when the app has been killed.
This commit is contained in:
@@ -35,14 +35,19 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
return CXProvider(configuration: configuration)
|
return CXProvider(configuration: configuration)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private weak var clientProxy: ClientProxyProtocol?
|
private weak var clientProxy: ClientProxyProtocol? {
|
||||||
|
didSet {
|
||||||
|
// There's a race condition where a call starts when the app has been killed and the
|
||||||
|
// observation set in `incomingCallID` occurs *before* the user session is restored.
|
||||||
|
// So observe when the client proxy is set to fix this (the method guards for the call).
|
||||||
|
Task { await observeIncomingCallRoomInfo() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var incomingCallRoomInfoCancellable: AnyCancellable?
|
||||||
private var incomingCallID: CallID? {
|
private var incomingCallID: CallID? {
|
||||||
didSet {
|
didSet {
|
||||||
Task {
|
Task { await observeIncomingCallRoomInfo() }
|
||||||
await observeIncomingCallRoomStateUpdates()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +265,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
func tearDownCallSession(sendEndCallAction: Bool = true) {
|
private func tearDownCallSession(sendEndCallAction: Bool = true) {
|
||||||
if sendEndCallAction, let ongoingCallID {
|
if sendEndCallAction, let ongoingCallID {
|
||||||
let transaction = CXTransaction(action: CXEndCallAction(call: ongoingCallID.callKitID))
|
let transaction = CXTransaction(action: CXEndCallAction(call: ongoingCallID.callKitID))
|
||||||
callController.request(transaction) { error in
|
callController.request(transaction) { error in
|
||||||
@@ -273,30 +278,35 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
ongoingCallID = nil
|
ongoingCallID = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func observeIncomingCallRoomStateUpdates() async {
|
private func observeIncomingCallRoomInfo() async {
|
||||||
cancellables.removeAll()
|
incomingCallRoomInfoCancellable = nil
|
||||||
|
|
||||||
guard let clientProxy, let incomingCallID else {
|
guard let incomingCallID else {
|
||||||
|
MXLog.info("No incoming call to observe for.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let clientProxy else {
|
||||||
|
MXLog.warning("A ClientProxy is needed to fetch the room.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard case let .joined(roomProxy) = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
|
guard case let .joined(roomProxy) = await clientProxy.roomForIdentifier(incomingCallID.roomID) else {
|
||||||
|
MXLog.warning("Failed to fetch a joined room for the incoming call.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
roomProxy.subscribeToRoomInfoUpdates()
|
roomProxy.subscribeToRoomInfoUpdates()
|
||||||
|
|
||||||
// There's no incoming event for call cancellations so try to infer
|
incomingCallRoomInfoCancellable = roomProxy
|
||||||
// it from what we have. If the call is running before subscribing then wait
|
|
||||||
// for it to change to `false` otherwise wait for it to turn `true` before
|
|
||||||
// changing to `false`
|
|
||||||
let isCallOngoing = roomProxy.infoPublisher.value.hasRoomCall
|
|
||||||
|
|
||||||
roomProxy
|
|
||||||
.infoPublisher
|
.infoPublisher
|
||||||
.compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) }
|
.compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) }
|
||||||
.removeDuplicates { $0 == $1 }
|
.removeDuplicates { $0 == $1 }
|
||||||
.dropFirst(isCallOngoing ? 0 : 1)
|
.drop(while: { hasRoomCall, _ in
|
||||||
|
// Filter all updates before hasRoomCall becomes `true`. Then we can correctly
|
||||||
|
// detect its change to `false` to stop ringing when the caller hangs up.
|
||||||
|
!hasRoomCall
|
||||||
|
})
|
||||||
.sink { [weak self] hasOngoingCall, activeRoomCallParticipants in
|
.sink { [weak self] hasOngoingCall, activeRoomCallParticipants in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
@@ -305,17 +315,16 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
|
|||||||
if !hasOngoingCall {
|
if !hasOngoingCall {
|
||||||
MXLog.info("Call cancelled by remote")
|
MXLog.info("Call cancelled by remote")
|
||||||
|
|
||||||
cancellables.removeAll()
|
incomingCallRoomInfoCancellable = nil
|
||||||
endUnansweredCallTask?.cancel()
|
endUnansweredCallTask?.cancel()
|
||||||
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .remoteEnded)
|
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .remoteEnded)
|
||||||
} else if participants.contains(roomProxy.ownUserID) {
|
} else if participants.contains(roomProxy.ownUserID) {
|
||||||
MXLog.info("Call anwered elsewhere")
|
MXLog.info("Call answered elsewhere")
|
||||||
|
|
||||||
cancellables.removeAll()
|
incomingCallRoomInfoCancellable = nil
|
||||||
endUnansweredCallTask?.cancel()
|
endUnansweredCallTask?.cancel()
|
||||||
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .answeredElsewhere)
|
callProvider.reportCall(with: incomingCallID.callKitID, endedAt: nil, reason: .answeredElsewhere)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user