Skip to main content
Monitor participant activities with ParticipantEventListeners. This listener provides callbacks for participant join/leave events, audio/video state changes, hand raise actions, screen sharing, recording, and more.

Prerequisites

Register Listener

Register a ParticipantEventListeners to receive participant event callbacks:
final callSession = CallSession.getInstance();

callSession?.addParticipantEventListener(ParticipantEventListeners(
  onParticipantJoined: (Participant participant) {
    debugPrint("${participant.name} joined the call");
  },
  onParticipantLeft: (Participant participant) {
    debugPrint("${participant.name} left the call");
  },
  onParticipantListChanged: (List<Participant> participants) {
    debugPrint("Participant list updated: ${participants.length} participants");
  },
  // Additional callbacks...
));
Flutter listeners are not lifecycle-aware. You must manually remove listeners in your widget’s dispose() method to prevent memory leaks.

Participant Object

The Participant object contains information about a call participant:
PropertyTypeDescription
uidStringUnique identifier of the participant
nameStringDisplay name of the participant
avatarStringAvatar URL of the participant
isAudioMutedboolWhether audio is muted
isVideoPausedboolWhether video is paused
isHandRaisedboolWhether hand is raised
isScreenSharingboolWhether screen is being shared

Callbacks

onParticipantJoined

Triggered when a new participant joins the call.
onParticipantJoined: (Participant participant) {
  debugPrint("${participant.name} joined the call");
  // Show join notification
  // Update participant grid
}
Use Cases:
  • Display join notification/toast
  • Update participant count
  • Play join sound

onParticipantLeft

Triggered when a participant leaves the call.
onParticipantLeft: (Participant participant) {
  debugPrint("${participant.name} left the call");
  // Show leave notification
  // Update participant grid
}
Use Cases:
  • Display leave notification
  • Update participant count
  • Play leave sound

onParticipantListChanged

Triggered when the participant list changes (join, leave, or any update).
onParticipantListChanged: (List<Participant> participants) {
  debugPrint("Participants: ${participants.length}");
  // Update participant list UI
  setState(() {
    _participants = participants;
  });
}
Use Cases:
  • Refresh participant list/grid
  • Update participant count badge
  • Sync local state with server

onParticipantAudioMuted

Triggered when a participant mutes their audio.
onParticipantAudioMuted: (Participant participant) {
  debugPrint("${participant.name} muted their audio");
  // Show muted indicator on participant tile
}

onParticipantAudioUnmuted

Triggered when a participant unmutes their audio.
onParticipantAudioUnmuted: (Participant participant) {
  debugPrint("${participant.name} unmuted their audio");
  // Hide muted indicator on participant tile
}

onParticipantVideoPaused

Triggered when a participant pauses their video.
onParticipantVideoPaused: (Participant participant) {
  debugPrint("${participant.name} paused their video");
  // Show avatar or placeholder instead of video
}

onParticipantVideoResumed

Triggered when a participant resumes their video.
onParticipantVideoResumed: (Participant participant) {
  debugPrint("${participant.name} resumed their video");
  // Show video stream
}

onParticipantHandRaised

Triggered when a participant raises their hand.
onParticipantHandRaised: (Participant participant) {
  debugPrint("${participant.name} raised their hand");
  // Show hand raised indicator
  // Optionally play notification sound
}

onParticipantHandLowered

Triggered when a participant lowers their hand.
onParticipantHandLowered: (Participant participant) {
  debugPrint("${participant.name} lowered their hand");
  // Hide hand raised indicator
}

onParticipantStartedScreenShare

Triggered when a participant starts sharing their screen.
onParticipantStartedScreenShare: (Participant participant) {
  debugPrint("${participant.name} started screen sharing");
  // Switch to screen share view
  // Show screen share indicator
}

onParticipantStoppedScreenShare

Triggered when a participant stops sharing their screen.
onParticipantStoppedScreenShare: (Participant participant) {
  debugPrint("${participant.name} stopped screen sharing");
  // Switch back to normal view
  // Hide screen share indicator
}

onParticipantStartedRecording

Triggered when a participant starts recording the session.
onParticipantStartedRecording: (Participant participant) {
  debugPrint("${participant.name} started recording");
  // Show recording indicator
  // Notify other participants
}

onParticipantStoppedRecording

Triggered when a participant stops recording the session.
onParticipantStoppedRecording: (Participant participant) {
  debugPrint("${participant.name} stopped recording");
  // Hide recording indicator
}

onDominantSpeakerChanged

Triggered when the dominant speaker changes (the participant currently speaking the loudest).
onDominantSpeakerChanged: (Participant participant) {
  debugPrint("${participant.name} is now the dominant speaker");
  // Highlight the dominant speaker's tile
  // Auto-focus on dominant speaker in spotlight mode
}
Use Cases:
  • Highlight active speaker in UI
  • Auto-switch spotlight to dominant speaker
  • Show speaking indicator animation

Complete Example

Here’s a complete example handling all participant events:
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
import 'package:flutter/material.dart';

class CallScreen extends StatefulWidget {
  const CallScreen({super.key});

  @override
  State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
  CallSession? _callSession;
  ParticipantEventListeners? _participantEventListener;
  List<Participant> _participants = [];

  @override
  void initState() {
    super.initState();
    _callSession = CallSession.getInstance();
    _setupParticipantEventListener();
  }

  void _setupParticipantEventListener() {
    _participantEventListener = ParticipantEventListeners(
      onParticipantJoined: (Participant participant) {
        _showSnackBar("${participant.name} joined");
      },
      onParticipantLeft: (Participant participant) {
        _showSnackBar("${participant.name} left");
      },
      onParticipantListChanged: (List<Participant> participants) {
        if (mounted) {
          setState(() {
            _participants = participants;
          });
        }
      },
      onParticipantAudioMuted: (Participant participant) {
        debugPrint("${participant.name} muted audio");
      },
      onParticipantAudioUnmuted: (Participant participant) {
        debugPrint("${participant.name} unmuted audio");
      },
      onParticipantVideoPaused: (Participant participant) {
        debugPrint("${participant.name} paused video");
      },
      onParticipantVideoResumed: (Participant participant) {
        debugPrint("${participant.name} resumed video");
      },
      onParticipantHandRaised: (Participant participant) {
        _showSnackBar("${participant.name} raised their hand");
      },
      onParticipantHandLowered: (Participant participant) {
        debugPrint("${participant.name} lowered their hand");
      },
      onParticipantStartedScreenShare: (Participant participant) {
        _showSnackBar("${participant.name} is sharing their screen");
      },
      onParticipantStoppedScreenShare: (Participant participant) {
        debugPrint("${participant.name} stopped screen sharing");
      },
      onParticipantStartedRecording: (Participant participant) {
        _showSnackBar("${participant.name} started recording");
      },
      onParticipantStoppedRecording: (Participant participant) {
        debugPrint("${participant.name} stopped recording");
      },
      onDominantSpeakerChanged: (Participant participant) {
        debugPrint("${participant.name} is now the dominant speaker");
      },
    );

    _callSession?.addParticipantEventListener(_participantEventListener!);
  }

  void _showSnackBar(String message) {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(message)),
      );
    }
  }

  @override
  void dispose() {
    // Must manually remove listener to prevent memory leaks
    if (_participantEventListener != null) {
      _callSession?.removeParticipantEventListener(_participantEventListener!);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text("Participants: ${_participants.length}"),
          Expanded(
            child: ListView.builder(
              itemCount: _participants.length,
              itemBuilder: (context, index) {
                final participant = _participants[index];
                return ListTile(
                  title: Text(participant.name ?? ""),
                  subtitle: Text(participant.uid ?? ""),
                  trailing: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      if (participant.isAudioMuted == true)
                        const Icon(Icons.mic_off, color: Colors.red),
                      if (participant.isVideoPaused == true)
                        const Icon(Icons.videocam_off, color: Colors.red),
                      if (participant.isHandRaised == true)
                        const Icon(Icons.back_hand, color: Colors.orange),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Remove Listener

Remove the listener in your widget’s dispose() method to prevent memory leaks:
@override
void dispose() {
  if (_participantEventListener != null) {
    _callSession?.removeParticipantEventListener(_participantEventListener!);
  }
  super.dispose();
}

Callbacks Summary

CallbackParameterDescription
onParticipantJoinedParticipantA participant joined the call
onParticipantLeftParticipantA participant left the call
onParticipantListChangedList<Participant>Participant list was updated
onParticipantAudioMutedParticipantA participant muted their audio
onParticipantAudioUnmutedParticipantA participant unmuted their audio
onParticipantVideoPausedParticipantA participant paused their video
onParticipantVideoResumedParticipantA participant resumed their video
onParticipantHandRaisedParticipantA participant raised their hand
onParticipantHandLoweredParticipantA participant lowered their hand
onParticipantStartedScreenShareParticipantA participant started screen sharing
onParticipantStoppedScreenShareParticipantA participant stopped screen sharing
onParticipantStartedRecordingParticipantA participant started recording
onParticipantStoppedRecordingParticipantA participant stopped recording
onDominantSpeakerChangedParticipantThe dominant speaker changed

Next Steps

Media Events Listener

Handle local media state changes

Participant Actions

Control other participants