f
hi
t
Thanks for reaching out, hardeep. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Hi — thanks for reaching out! How can I help you today? If you’re reporting an issue, please share: • What you’re trying to do (e.g., join a room, publish video, record) • Any error messages or screenshots • Your project ID and room ID (if applicable) • Browser/OS and SDK version you’re using If you’re asking about setup or features, tell me which SDK/language (Web, iOS, Android, server) and I’ll guide you. , with interactive elements
f
facing an issue: i was in a meeting with 5 users, users left in between and livekit gives participant disconnect event, but later when i fetch remote participants from room:
Copy code
// 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 user
t
Thanks — I’m responding to the issue where participants who left still appear as active in
room.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 elements
f
how can i get the users who are currently live in the meeting
t
You’re asking how to get only the participants who are currently live/connected in the meeting. Filter the room’s participants by their connection/state rather than relying on the map length. Example (Flutter/Dart):
Copy code
// 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 elements
f
Copy code
enum 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 sdk
t
Thanks — I’m responding to how to get only users who are currently live using the
ParticipantState
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):
Copy code
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 elements
f
Copy code
flutter: 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
Copy code
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 ago
t
Thanks — I’m looking at your logs showing repeated TrackUnpublishedEvent + ParticipantDisconnectedEvent calls, yet the final room participant list still shows several participants with
ParticipantState.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
Copy code
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 elements
f
currently im assuming that if livekit sdk telling me any participant as active then that must be present in the livekit, but in reality it has left the meeting few minutes before. is it a bug from livekit flutter sdk?
t
Thanks — I’m responding to your question: “Is this a bug in the LiveKit Flutter SDK?” after seeing participants marked
active
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 elements
f
which participants i must treat as real ones?
t
Good question — here’s a concise rule you can follow. Treat a participant as “real/live” when: • Their
state == 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 elements
f
once any ghost participant appears, it doesnt cleanup from sdk map even after 30 minutes
t
Thanks — I’m responding to your note that ghost participants remain in
room.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 elements
f
version is 2.7.0
t
Thanks — I’m responding about which participants to treat as real/live given you’re on Flutter SDK v2.7.0. Short answer • Treat participants with
state == 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:
Copy code
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 elements
f
we are initializing room events before room.connect. could u report a bug to livekit client=
t
I don't have the answer you're looking for. You could also try asking your question: • in one of the other Slack channels or • to https://deepwiki.com/livekit/livekit_composite which is trained on all LiveKit source code If you find the answer, please post it here to help others!
f
could it be due to self hosted server?