flaky-jelly-17724
05/06/2026, 10:05 AMtall-airline-16756
05/06/2026, 10:05 AMtall-airline-16756
05/06/2026, 10:05 AMflaky-jelly-17724
05/06/2026, 10:09 AM// final ap = _ctrl?.room?.remoteParticipants.values ?? [];
// final lp = _ctrl?.room?.localParticipant;
// debugPrint('local participant name: ${lp?.name}, sid: ${lp?.sid}, identity: ${lp?.identity}');
// for(var a in ap) {
// debugPrint('remote participant name: ${a.name}, sid: ${a.sid}, identity: ${a.identity}, state: ${a.state}');
// }
sometimes it gives the left participant as active so it become ghost usertall-airline-16756
05/06/2026, 10:10 AMroom.remoteParticipants, causing ghost users.
Short answer: this can happen when the server and client become out of sync (for example a `STATE_MISMATCH`/unclean disconnect). The server may still have the participant marked as connected until it times out, so your client can see a participant object with an active state even after you received a disconnect event.
What to check and next steps
1. Confirm the disconnect reason you receive in the participant disconnect event (log the reason enum). If you see STATE_MISMATCH or similar, it indicates an unclean/networked disconnect and the server will remove the participant after its timeout.
2. Rely on participant state rather than presence in the map:
◦ Continue to read participant.state (or the SDK’s participant state property) and treat anything other than `connected`/`active` as left.
◦ If your UI depends on the map size, filter remoteParticipants by state == connected before rendering.
1. Listen to room/participant lifecycle events:
◦ Ensure you handle both participantDisconnected (or equivalent) and participantUpdated events — sometimes state changes arrive on participantUpdated.
◦ If you get a disconnect event but the map still contains the participant, wait for the final participant-removed event or state change before permanently removing UI elements.
1. Consider a small grace period before removing UI:
◦ For unclean disconnects, add a short timeout (e.g., 20–25s) to let the server finalize the participant removal, or remove immediately if the disconnect reason is explicit.
1. If you need determinism for your app:
◦ On disconnect events, you can proactively mark that participant as left in your app state (e.g., set a left flag) and hide them while still keeping the SDK object until the server removes it.
If you’d like, tell me:
• Which Flutter LiveKit SDK version you’re using
• The disconnect reason you observe (log value), and
• Whether you’re already filtering by participant.state before rendering
I can then suggest exact code changes or a short patch for your UI update logic.
Sources: Understanding the STATE_MISMATCH Disconnect Reason, with interactive elementsflaky-jelly-17724
05/06/2026, 10:13 AMtall-airline-16756
05/06/2026, 10:14 AM// get all remote participants map -> values
final allRemote = room.remoteParticipants.values;
// filter to only connected participants
final liveRemote = allRemote.where((p) => p.state == ParticipantState.connected).toList();
// include local participant if they are connected
final local = room.localParticipant;
final liveParticipants = [
if (local != null && local.state == ParticipantState.connected) local,
...liveRemote,
];
// use liveParticipants for UI or logic
Notes:
• Use ParticipantState.connected (or the exact enum name in your SDK) to detect live users.
• Some disconnects are unclean and the server may take a short timeout to remove a participant; filtering by state avoids showing ghost users.
• If you need immediate removal on client disconnect events, mark them in your app state on the disconnect event and hide them until the SDK removes them.
Tell me your Flutter LiveKit SDK version if you want a tailored snippet or help integrating this into your UI update logic.
, with interactive elementsflaky-jelly-17724
05/06/2026, 12:29 PMenum ParticipantState {
/// Websocket is connected, but no offer has been sent yet
joining,
/// Server has received the client's offer
joined,
/// ICE connectivity has been established
active,
/// Websocket has disconnected
disconnected,
/// Unknown state
unknown,
}
this is the enum in the latest client sdktall-airline-16756
05/06/2026, 12:29 PMParticipantState you shared.
Use the active state to represent participants with established ICE connectivity (live). `joining`/`joined` are not yet media-active, and disconnected means they’ve left.
Example (Flutter/Dart):
final allRemote = room.remoteParticipants.values;
final liveRemote = allRemote.where((p) => p.state == ParticipantState.active).toList();
final local = room.localParticipant;
final liveParticipants = [
if (local != null && local.state == ParticipantState.active) local,
...liveRemote,
];
Notes:
• Filter by ParticipantState.active before rendering to avoid ghost users.
• Also listen for participant lifecycle events (`participantJoined`/`participantUpdated`/`participantDisconnected`) and update your UI when states change.
• If you see unclean disconnects (e.g., server timeout), you can temporarily hide a participant on participantDisconnected while the server finalizes removal.
If you want, share your Flutter LiveKit SDK version and the event handlers you’re using and I’ll give a tailored code snippet to integrate this into your UI update flow.
, with interactive elementsflaky-jelly-17724
05/06/2026, 12:33 PMflutter: TrackUnpublishedEvent: PA_Z7CZw9WW3vo2, 2026-05-06 17:37:43.041034
flutter: ParticipantDisconnectedEvent: PA_Z7CZw9WW3vo2, 2026-05-06 17:37:43.045666
flutter: TrackUnpublishedEvent: PA_4MJ9XteQmpwu, 2026-05-06 17:38:47.634131
flutter: ParticipantDisconnectedEvent: PA_4MJ9XteQmpwu, 2026-05-06 17:38:47.636615
flutter: TrackUnpublishedEvent: PA_6ZQ9iT7qFPoY, 2026-05-06 17:40:04.967874
flutter: ParticipantDisconnectedEvent: PA_6ZQ9iT7qFPoY, 2026-05-06 17:40:04.970077
flutter: TrackUnpublishedEvent: PA_puNVtskEyg5b, 2026-05-06 17:41:05.824072
flutter: ParticipantDisconnectedEvent: PA_puNVtskEyg5b, 2026-05-06 17:41:05.827290
flutter: TrackUnpublishedEvent: PA_Fqb9w5VY5ZBX, 2026-05-06 17:42:32.638828
flutter: ParticipantDisconnectedEvent: PA_Fqb9w5VY5ZBX, 2026-05-06 17:42:32.640219
flutter: TrackUnpublishedEvent: PA_wJcMWc987sX6, 2026-05-06 17:43:13.154681
flutter: ParticipantDisconnectedEvent: PA_wJcMWc987sX6, 2026-05-06 17:43:13.156480
flutter: ParticipantDisconnectedEvent: PA_WTLR6DLJVD3f, 2026-05-06 17:44:06.346976
flutter: TrackUnpublishedEvent: PA_WTLR6DLJVD3f, 2026-05-06 17:44:06.347726
flutter: TrackUnpublishedEvent: PA_WBKXUDyFsE3D, 2026-05-06 17:45:46.375085
flutter: ParticipantDisconnectedEvent: PA_WBKXUDyFsE3D, 2026-05-06 17:45:46.381861
flutter: ParticipantDisconnectedEvent: PA_KkV8wRCyPHpv, 2026-05-06 17:46:35.596998
flutter: TrackUnpublishedEvent: PA_KkV8wRCyPHpv, 2026-05-06 17:46:35.598630
flutter: TrackUnpublishedEvent: PA_frsFCdXB3gF5, 2026-05-06 17:48:04.994162
flutter: ParticipantDisconnectedEvent: PA_frsFCdXB3gF5, 2026-05-06 17:48:04.996166
flutter: TrackUnpublishedEvent: PA_aGdkMZvMg6e9, 2026-05-06 17:48:34.032723
flutter: ParticipantDisconnectedEvent: PA_aGdkMZvMg6e9, 2026-05-06 17:48:34.033127
flutter: TrackUnpublishedEvent: PA_V8SbhkeghLEY, 2026-05-06 17:49:45.606084
flutter: ParticipantDisconnectedEvent: PA_V8SbhkeghLEY, 2026-05-06 17:49:45.606992
flutter: TrackUnpublishedEvent: PA_7V4YaGkdw6uQ, 2026-05-06 17:50:29.811209
flutter: ParticipantDisconnectedEvent: PA_7V4YaGkdw6uQ, 2026-05-06 17:50:29.812653
flutter: TrackUnpublishedEvent: PA_GdPfYGav4EMf, 2026-05-06 17:51:01.344876
flutter: ParticipantDisconnectedEvent: PA_GdPfYGav4EMf, 2026-05-06 17:51:01.349408
flutter: TrackUnpublishedEvent: PA_buAtAv99TwSU, 2026-05-06 17:51:29.817563
flutter: ParticipantDisconnectedEvent: PA_buAtAv99TwSU, 2026-05-06 17:51:29.819282
flutter: TrackUnpublishedEvent: PA_3rf8YdcGuoBz, 2026-05-06 17:51:57.459584
flutter: ParticipantDisconnectedEvent: PA_3rf8YdcGuoBz, 2026-05-06 17:51:57.460873
flutter: TrackUnpublishedEvent: PA_hEEce4DoUFD8, 2026-05-06 17:52:23.272114
flutter: ParticipantDisconnectedEvent: PA_hEEce4DoUFD8, 2026-05-06 17:52:23.284042
flutter: TrackUnpublishedEvent: PA_CSifJaLgSs5C, 2026-05-06 17:53:15.230854
flutter: ParticipantDisconnectedEvent: PA_CSifJaLgSs5C, 2026-05-06 17:53:15.231143
flutter: TrackUnpublishedEvent: PA_JqqivgkWYdgB, 2026-05-06 17:53:20.937259
flutter: ParticipantDisconnectedEvent: PA_JqqivgkWYdgB, 2026-05-06 17:53:20.937956
flutter: local participant name: IN HR 03 Begu, sid: PA_ZWjrAtUhQFsA, identity: 80a38e6c19828ce11301c931cdbcb6ed
flutter: remote participant name: IN PB 18 Malout, sid: PA_WTLR6DLJVD3f, identity: b3cc33dc6091581d273d63172a42187c, state: ParticipantState.active
flutter: remote participant name: IN PB 18 Malout, sid: PA_KkV8wRCyPHpv, identity: 2452df2899e1fb892a0234023ff7ba4b, state: ParticipantState.active
flutter: remote participant name: IN PB 18 Malout, sid: PA_aGdkMZvMg6e9, identity: 145509dd1a79157a5be184c1e498a133, state: ParticipantState.active
flutter: recent deleted sids {PA_Z7CZw9WW3vo2, PA_4MJ9XteQmpwu, PA_6ZQ9iT7qFPoY, PA_puNVtskEyg5b, PA_Fqb9w5VY5ZBX, PA_wJcMWc987sX6, PA_WTLR6DLJVD3f, PA_WBKXUDyFsE3D, PA_KkV8wRCyPHpv, PA_frsFCdXB3gF5, PA_aGdkMZvMg6e9, PA_8rQvigtQm8ia, PA_V8SbhkeghLEY, PA_7V4YaGkdw6uQ, PA_GdPfYGav4EMf, PA_buAtAv99TwSU, PA_3rf8YdcGuoBz, PA_hEEce4DoUFD8, PA_CSifJaLgSs5C, PA_JqqivgkWYdgB}
if u see these logs: i joined and left from a device for several time, each time i received 'TrackUnpublishedEvent' and 'ParticipantDisonnectedEvent' but at the end i asked for room participants: then it showed 3 participants as active which were left few minutes agotall-airline-16756
05/06/2026, 12:33 PMParticipantState.active. I can help you make the participant list deterministic.
What’s happening (brief)
• You are receiving disconnect events, but the client and server can momentarily be out of sync after unclean disconnects or repeated reconnects. That can leave participant objects in the room map with active until the server finalizes removal or the local SDK updates state.
Practical fixes to reliably get only currently live users
1. Filter by state active when building your live list
final liveRemote = room.remoteParticipants.values
.where((p) => p.state == ParticipantState.active)
.toList();
final liveParticipants = [
if (room.localParticipant != null && room.localParticipant!.state == ParticipantState.active)
room.localParticipant!,
...liveRemote,
];
1. Update UI on lifecycle events
• Handle participantDisconnected, participantUpdated, and trackUnpublished events.
• On participantUpdated, re-filter and update UI (state changes can arrive on update events).
1. Prefer participant state over presence in the map
• The presence of a participant object in remoteParticipants does not guarantee media connectivity; use p.state == ParticipantState.active as your single source of truth for “live”.
1. Handle unclean disconnects / quick reconnect loops
• If you see many rapid connect/disconnects, add a short grace window (e.g., 15–25s) before permanently removing a participant from your app UI, or immediately hide them on participantDisconnected while still waiting for the final removal.
• Optionally keep a local set of recently-removed SIDs and ignore them when rebuilding the UI until the server removes them.
1. Debug helpers to confirm flow
• Log the disconnect reason/enum for each ParticipantDisconnectedEvent.
• Log participant updates (state changes) to see when `active`→`disconnected` arrives.
If you want, I can:
• Provide a small stateful widget snippet to integrate event handling and filtering into your UI.
• Review the event handler code you’re currently using to spot where UI updates might be out-of-order.
Please share your Flutter LiveKit SDK version and the participant event handler code if you want a tailored snippet.
, with interactive elementsflaky-jelly-17724
05/06/2026, 12:40 PMtall-airline-16756
05/06/2026, 12:40 PMactive even though they left minutes earlier.
Short answer: this is not necessarily a bug in the SDK. Participant objects can remain in the client’s map with active when the client and server are briefly out of sync (for example after unclean disconnects, rapid reconnects, or network interruptions). The SDK exposes the state the client currently knows; that state can lag the server’s final removal of the participant.
Why this happens (concise)
• Unclean disconnects or network loss can cause the server to take time to finalize removal (server-side timeout). During that window the SDK may still show active.
• The disconnect reason can be STATE_MISMATCH or similar when media/ICE keep-alives are lost; that reflects an out-of-sync media state rather than an immediate server-side removal.
• State updates can arrive on participantUpdated after you already got `participantDisconnected`/`trackUnpublished`, so transient inconsistencies are possible.
Recommended, reliable approach
1. Treat ParticipantState.active as “media/ICE established” but not as the sole source of truth for immediate presence.
2. Build your live list by filtering for state == ParticipantState.active and update it on these events: participantUpdated, participantDisconnected, and trackUnpublished.
3. Add a short grace/cleanup policy:
◦ Option A: Hide participants immediately on participantDisconnected, and remove them from UI when the SDK/map actually drops them.
◦ Option B: Wait a short timeout (15–25s) after participantDisconnected before removing to allow server-side cleanup.
1. Track disconnect reasons in logs (the reason enum) — it helps confirm unclean disconnects like STATE_MISMATCH.
Next steps I can help with
• Review your SDK version and current event handlers to give a concrete code patch.
• Provide a small Flutter widget/state snippet implementing filtering + event-driven updates + optional grace timeout.
Please share your Flutter LiveKit SDK version and the participant event handler code you’re using and I’ll provide the tailored snippet.
, with interactive elementsflaky-jelly-17724
05/06/2026, 12:43 PMtall-airline-16756
05/06/2026, 12:43 PMstate == ParticipantState.active (ICE/connectivity established), and
• You have not received a participantDisconnected (or a disconnect reason) for them recently.
Best practice to be deterministic
• Use state == ParticipantState.active as the primary check when building your live list.
• Update the live list on these events: participantUpdated, participantDisconnected, trackUnpublished, and the SDK’s participant-removed event.
• Handle unclean disconnects: when you get participantDisconnected, temporarily hide that participant immediately (or mark them as leaving) and stop treating them as live; then remove them from UI when the SDK/map actually drops them.
• Optional: keep a short grace window (15–25s) for server-side cleanup for flaky networks; or maintain a local set of recently-removed SIDs to ignore if the SDK map still contains them.
Implementation pattern (Flutter/Dart)
• Filter remoteParticipants by p.state == ParticipantState.active each time you update UI.
• On participantDisconnected, mark the participant as left in your app state (or remove from visible list) even if the SDK object remains.
If you want, share your event-handler code and SDK version and I’ll give a short snippet showing integration.
, with interactive elementsflaky-jelly-17724
05/06/2026, 12:45 PMtall-airline-16756
05/06/2026, 12:46 PMroom.remoteParticipants for 30+ minutes even after you received ParticipantDisconnectedEvent.
Short answer
• Participant objects should be removed from room.remoteParticipants after a proper disconnect. If they persist that long despite disconnect events, this is not expected behavior and likely indicates either missed event handling, an SDK version bug, or an edge-case server/client sync issue.
What to check now (please provide these so I can help further)
1. Your Flutter LiveKit SDK version.
2. The code where you register room event listeners (especially when/how you register participantDisconnected, participantUpdated, and participantRemoved handlers).
3. Logs showing the disconnect reason (if available) for those SIDs.
Immediate troubleshooting steps you can try
• Ensure event handlers are registered before calling room.connect() so you don’t miss lifecycle events.
• Log every participant update event (not just disconnect) to see if/when the SDK emits a state change from active → disconnected → removed.
• Confirm you are not holding strong references to participant objects in your own app state that prevent UI cleanup (the SDK map is managed internally).
• Upgrade to the latest Flutter SDK release if you are not already on it.
Workarounds you can implement now
• Treat participants as “not live” when you receive participantDisconnected and hide them in UI immediately, even if the SDK map still contains them.
• Maintain a local set of recently-removed SIDs and ignore those entries when building the live list until the SDK actually removes them.
• Filter live users by p.state == ParticipantState.active and exclude any SID present in your “recently disconnected” set.
If you share your SDK version and the event registration/handler code, I’ll:
• Review it for missed/misordered listener registration or common mistakes.
• Provide a small Flutter snippet that integrates logging, filtering by ParticipantState.active, and a simple recent-SID blacklist workaround. Sources: Understanding the STATE_MISMATCH Disconnect Reason, with interactive elementsflaky-jelly-17724
05/06/2026, 12:54 PMtall-airline-16756
05/06/2026, 12:54 PMstate == ParticipantState.active as live.
• Also stop treating a participant as live as soon as you receive participantDisconnected for their SID (even if the SDK map still contains the object).
Notes specific to v2.7.0 and your problem
• The SDK should remove participants from room.remoteParticipants after disconnect, but if ghost entries persist for 30+ minutes that is not expected.
• This can be caused by missed/misordered event registration, unclean disconnects, or an SDK bug in your version.
Immediate actions
1. Ensure you register room event listeners (especially participantDisconnected, participantUpdated, and participantRemoved) before calling room.connect().
2. Log the disconnect reason/enum for each ParticipantDisconnectedEvent to check for STATE_MISMATCH or other unclean disconnects.
3. Use this deterministic live check when rendering:
final liveRemote = room.remoteParticipants.values
.where((p) => p.state == ParticipantState.active && !recentlyDisconnected.contains(p.sid))
.toList();
1. Workaround for ghost participants:
◦ On participantDisconnected, add the SID to a recentlyDisconnected set and hide them immediately.
◦ Remove the SID from the set when the SDK actually removes the participant from the map or after a grace timeout (e.g., 15–25s).
If you want, share your event-registration and handler code (and any disconnect-reason logs) and I’ll review it and provide a short patch/snippet to ensure listeners aren’t missed and to implement the recent-SID workaround.
, with interactive elementsflaky-jelly-17724
05/06/2026, 12:57 PMtall-airline-16756
05/06/2026, 12:57 PMflaky-jelly-17724
05/07/2026, 5:00 AM