[iOS] CXSetHeldCallAction handler re-requests the same hold action, so unholding a call flips it between held and active until it ends

Chiusa Adatta ai principianti
#1,529 0 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

@Brazol ci sta già lavorando.

Dal 29/10/2026.

  • #1533 di @Brazol — integrata

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
dart, flutter, swift

Direzione di ricerca

Inizia nel package iOS stream_video_push_notification e segui StreamVideoCallkitManager.provider(_:perform:) insieme a StreamCallKitCallController.setHold(call:onHold:). Controlla i chiamanti di setHold e confronta il gestore della messa in attesa con quello dell’azione di silenziamento. Il lavoro è completato quando la ripresa genera un evento di messa in attesa, ripristina la sessione audio e non accoda un’altra transazione di messa in attesa.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Package: stream_video_push_notification (iOS)
Version: seen at 164832a0f29d1c0f6dd1edb9f2458bc568ad56ad, also in 1.7.x
Platform: iOS only
Device: iPhone, iOS 26.6

Description

StreamVideoCallkitManager.provider(_:perform: CXSetHeldCallAction) asks CallKit for a new CXSetHeldCallAction with the same value while it is still handling the current one:

public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
    guard let call = self.callController.callWithUUID(uuid: action.callUUID) else {
        action.fail()
        return
    }
    call.isOnHold = action.isOnHold
    call.isMuted = action.isOnHold
    self.callController.setHold(call: call, onHold: action.isOnHold) // <- requests a new transaction
    sendHoldEvent(action.callUUID.uuidString, action.isOnHold)
    action.fulfill()
}

StreamCallKitCallController.setHold(call:onHold:) builds a new CXSetHeldCallAction and sends it to CXCallController.request. So every hold action CallKit performs queues one more just like it.

  1. A system hold (for example accepting a cellular call with "Hold & Accept") leaves an extra "hold" transaction queued.
  2. When the call is unheld (from the app via holdCall(uuid, isOnHold: false), or by the system), the unhold queues an extra "unhold" transaction too.
  3. From then on, each pending transaction re-queues itself when performed, so CallKit keeps alternating hold and unhold.

The loop stops only when the CallKit call ends. After that the handler's callWithUUID guard fails and nothing is requested again.

The CXSetMutedCallAction handler doesn't do this. It updates state, sends the event and fulfills.

Steps to reproduce

  1. Start or join a call with CallKit (startOutgoingCall + setCallConnected, or an incoming call that is accepted).
  2. Receive a cellular call and choose Hold & Accept. The app gets ActionCallToggleHold(isOnHold: true).
  3. End the cellular call.
  4. Resume the held call: call pushNotificationManager.holdCall(uuid, isOnHold: false), or resume it from the native call UI.

Expected behavior

One ActionCallToggleHold(isOnHold: false). The call becomes active and its audio session is activated again.

Actual behavior

ActionCallToggleHold events for the call alternate between true and false every few milliseconds, with repeated ActionCallToggleAudioSession(isActive: false) events, until the call ends. Audio doesn't come back, and any UI driven by the hold state flickers.

Logs from the app (one holdCall(uuid, isOnHold: false) at 14:09:50.884):

14:09:30.495 ActionCallToggleHold BCDFA02E-… isOnHold: true
14:09:50.884 app: holdCall(bcdfa02e-…, isOnHold: false)
14:09:50.887 ActionCallToggleHold BCDFA02E-… isOnHold: false
14:09:50.888 ActionCallToggleHold BCDFA02E-… isOnHold: true
14:09:50.888 ActionCallToggleHold BCDFA02E-… isOnHold: false
14:09:50.894 ActionCallToggleHold BCDFA02E-… isOnHold: true
14:09:50.894 ActionCallToggleHold BCDFA02E-… isOnHold: false
…
14:09:51.241 ActionCallToggleAudioSession isActive: false
…  (400+ hold events in 8 s)
14:09:58.420 app leaves the call, CallKit call ends, events stop

Suggested fix

Don't request a transaction from the perform handler. CallKit is already performing this action, so the handler only needs to update local state, notify Dart and fulfill:

public func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
    guard let call = self.callController.callWithUUID(uuid: action.callUUID) else {
        action.fail()
        return
    }
    call.isOnHold = action.isOnHold
    call.isMuted = action.isOnHold
    sendHoldEvent(call.data.uuid, action.isOnHold)
    action.fulfill()
}

If setHold(call:onHold:) has no other callers, it can be removed. holdCall(call:onHold:) already covers holds the app requests.

Workaround

Don't unhold the CallKit call. To resume, end it with endCallByCid(cid, silent: true) and report the call again with a new uuid (startOutgoingCall + setCallConnected). Ending the call breaks the loop.

Lingua principale
Dart
Stelle
79
Fork
55
Merge medio
2g 10h
PR unite (30g)
132

Preparare l'ambiente

  • Nessun Dockerfile né file Docker Compose
  • Ha un modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di GetStream/stream-video-flutter

Tutte le issue di GetStream/stream-video-flutter

Issue simili

Altre issue su Dart

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.