Describe the bug
When an admin participant calls RemoteParticipant.mute(completionHandler:) to mute another participant in a group call, the server-side mute is fast (the didChangeMuteState isMuted=true delegate fires within ~300 ms), but the completion handler passed to mute(...) does not fire until ~45 seconds after the server-side event. Subsequent calls to mute(...) are serialized behind that delay — each one waiting for the previous call's completion handler before being dispatched to the server — so user-visible latency compounds: 45 s → 75 s → 105 s for the 1st, 2nd, 3rd consecutive mute attempts.
Two invariants in the captured data make this very clear:
- The gap between
didChangeMuteState isMuted=true and the corresponding mute() completion handler is a constant ~45 seconds across attempts.
- Attempt N's server-side mute event fires within ~200 ms of attempt N−1's completion handler — i.e. the SDK only dispatches the next request after the previous request's callback runs.
Exception or Stack Trace
N/A — no exception is thrown. This is a latency/serialization issue. The completion handler does eventually fire with success (no error), just very late.
To Reproduce
Steps to reproduce the behavior:
- Join a group call with two ACS participants. Designate one as admin.
- As admin, call
RemoteParticipant.mute(completionHandler:) on the other participant.
- Observe: the muted participant's
didChangeMuteState isMuted=true delegate fires within ~300 ms (server-side mute is fast), but the completionHandler you passed to mute(...) does not fire for ~45 seconds.
- Have the muted participant unmute themselves.
- As admin, call
mute(...) on the same participant again. The server-side mute event now takes ~30 s to fire, and the completion handler takes ~75 s.
- Have the participant unmute themselves again.
- As admin, call
mute(...) a third time. Each additional attempt adds another ~30 s of latency.
Observed timing across three consecutive attempts on the same participant in a single session:
| Attempt |
mute() call site → didChangeMuteState event |
mute() call site → completion handler |
| 1 |
~334 ms |
~45.0 s |
| 2 |
~29.9 s |
~74.7 s |
| 3 |
~33.5 s |
still pending after 30+ s |
Code Snippet
import AzureCommunicationCalling
import Combine
func muteRemote(_ remoteParticipant: RemoteParticipant) -> Future<Void, Error> {
Future { promise in
let start = CFAbsoluteTimeGetCurrent()
print("[mute] begin t=\(Date())")
remoteParticipant.mute { error in
let elapsedMs = Int((CFAbsoluteTimeGetCurrent() - start) * 1000)
print("[mute] end t=\(Date()) elapsedMs=\(elapsedMs) error=\(String(describing: error))")
if let error { promise(.failure(error)) } else { promise(.success(())) }
}
}
}
// RemoteParticipantDelegate
func remoteParticipant(_ remoteParticipant: RemoteParticipant, didChangeMuteState args: PropertyChangedEventArgs) {
print("[event] didChangeMuteState isMuted=\(remoteParticipant.isMuted) t=\(Date())")
}
Tap a "Mute" button as admin → wait for the remote participant to unmute themselves → tap "Mute" again. Compare the t= on [event] didChangeMuteState isMuted=true against [mute] end for each attempt.
Expected behavior
The completion handler passed to RemoteParticipant.mute(completionHandler:) should fire shortly after the server confirms the mute (i.e. shortly after didChangeMuteState isMuted=true), not 45 seconds later. Subsequent mute(...) calls for the same participant should be dispatched to the server independently, not serialized behind the previous call's completion handler.
Screenshots
N/A — the issue is in log timing rather than UI. A representative timeline of a reproduction is included above in the "To Reproduce" table.
Setup (please complete the following information):
- OS: iOS 26.4.2 (real device, not simulator)
- IDE: Xcode 26.5
- Version of the Library used:
AzureCommunicationCalling 2.15.1 (also reproduced on 2.18.3-beta.2)
Additional context
Impact: this affects admin-driven mute workflows (e.g. a telehealth provider muting a non-cooperating patient). The user-visible symptom is "mute works the first time, but on the second tap nothing happens for 30+ seconds." Each rapid retry compounds the latency, which makes the UX appear to lock up.
Information Checklist
Describe the bug
When an admin participant calls
RemoteParticipant.mute(completionHandler:)to mute another participant in a group call, the server-side mute is fast (thedidChangeMuteState isMuted=truedelegate fires within ~300 ms), but the completion handler passed tomute(...)does not fire until ~45 seconds after the server-side event. Subsequent calls tomute(...)are serialized behind that delay — each one waiting for the previous call's completion handler before being dispatched to the server — so user-visible latency compounds: 45 s → 75 s → 105 s for the 1st, 2nd, 3rd consecutive mute attempts.Two invariants in the captured data make this very clear:
didChangeMuteState isMuted=trueand the correspondingmute()completion handler is a constant ~45 seconds across attempts.Exception or Stack Trace
N/A — no exception is thrown. This is a latency/serialization issue. The completion handler does eventually fire with
success(no error), just very late.To Reproduce
Steps to reproduce the behavior:
RemoteParticipant.mute(completionHandler:)on the other participant.didChangeMuteState isMuted=truedelegate fires within ~300 ms (server-side mute is fast), but thecompletionHandleryou passed tomute(...)does not fire for ~45 seconds.mute(...)on the same participant again. The server-side mute event now takes ~30 s to fire, and the completion handler takes ~75 s.mute(...)a third time. Each additional attempt adds another ~30 s of latency.Observed timing across three consecutive attempts on the same participant in a single session:
mute()call site →didChangeMuteStateeventmute()call site → completion handlerCode Snippet
Tap a "Mute" button as admin → wait for the remote participant to unmute themselves → tap "Mute" again. Compare the
t=on[event] didChangeMuteState isMuted=trueagainst[mute] endfor each attempt.Expected behavior
The completion handler passed to
RemoteParticipant.mute(completionHandler:)should fire shortly after the server confirms the mute (i.e. shortly afterdidChangeMuteState isMuted=true), not 45 seconds later. Subsequentmute(...)calls for the same participant should be dispatched to the server independently, not serialized behind the previous call's completion handler.Screenshots
N/A — the issue is in log timing rather than UI. A representative timeline of a reproduction is included above in the "To Reproduce" table.
Setup (please complete the following information):
AzureCommunicationCalling2.15.1 (also reproduced on 2.18.3-beta.2)Additional context
Impact: this affects admin-driven mute workflows (e.g. a telehealth provider muting a non-cooperating patient). The user-visible symptom is "mute works the first time, but on the second tap nothing happens for 30+ seconds." Each rapid retry compounds the latency, which makes the UX appear to lock up.
Information Checklist