Local Participant·续2
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onScreenShareStartFailed(reason: String) {
// screen share failed to start
}
override fun onScreenShareUpdate(isEnabled: Boolean) {
if (isEnabled) {
// screen share is enabled
} else {
// screen share is disabled
}
}
})
meeting.addSelfEventListener(self)
extension MeetingViewModel: RtkSelfEventListener {
func onRemovedFromMeeting() {
// User was removed from the meeting (kicked or meeting ended)
// Display alert or navigate to exit screen
}
func onMeetingRoomDisconnected() {
// Lost connection to the meeting room
// Display reconnection UI or error message
}
}
You can also monitor the roomJoined property for state changes:
let isInMeeting = meeting.localUser.roomJoined
const screenShareEnabled = useRealtimeKitSelector(
(m) => m.self.screenShareEnabled,
);
const screenShareTracks = useRealtimeKitSelector(
(m) => m.self.screenShareTracks,
);
useEffect(() => {
if (screenShareEnabled && screenShareTracks) {
console.log("Screen sharing is active");
// Handle screen share tracks
}
}, [screenShareEnabled, screenShareTracks]);
Device update
Fires when the active device changes:
meeting.self.on("deviceUpdate", ({ device }) => {
// Handle device change
if (device.kind === "audioinput") {
console.log("Microphone changed:", device.label);
} else if (device.kind === "videoinput") {
console.log("Camera changed:", device.label);
} else if (device.kind === "audiooutput") {
console.log("Speaker changed:", device.label);
}
});
useEffect(() => {
if (!meeting) return;
const handleDeviceUpdate = ({ device }) => {
if (device.kind === "audioinput") {
console.log("Microphone changed:", device.label);
} else if (device.kind === "videoinput") {
console.log("Camera changed:", device.label);
}
};
meeting.self.on("deviceUpdate", handleDeviceUpdate);
return () => {
meeting.self.off("deviceUpdate", handleDeviceUpdate);
};
}, [meeting]);
meeting.self.addSelfEventListener(object : RtkSelfEventListener() {
override fun onAudioDeviceChanged(device: AudioDevice) {
// Handle audio device change
println("Audio device changed: ${device.label}")
}
override fun onVideoDeviceChanged(device: VideoDevice) {
// Handle video device change
println("Video device changed: ${device.label}")
}
})
meeting.self.addSelfEventListener(self)
// RtkSelfEventListener implementation
func onAudioDeviceChanged(device: AudioDevice) {
// Handle audio device change
print("Audio device changed: \(device.label)")
}
func onVideoDeviceChanged(device: VideoDevice) {
// Handle video device change
print("Video device changed: \(device.label)")
}
useEffect(() => {
if (!meeting) return;
const handleDeviceUpdate = ({ device }) => {
if (device.kind === "audioinput") {
console.log("Microphone changed:", device.label);
} else if (device.kind === "videoinput") {
console.log("Camera changed:", device.label);
}
};
meeting.self.on("deviceUpdate", handleDeviceUpdate);
return () => {
meeting.self.off("deviceUpdate", handleDeviceUpdate);
};
}, [meeting]);
Device List Update
Triggered when the list of available devices changes (device plugged in or out):
meeting.self.on("deviceListUpdate", ({ added, removed, devices }) => {
console.log("Device list updated");
console.log("Added devices:", added);
console.log("Removed devices:", removed);
console.log("All devices:", devices);
});
useEffect(() => {
if (!meeting) return;
const handleDeviceListUpdate = ({ added, removed, devices }) => {
console.log("Device list updated");
console.log("Added devices:", added);
console.log("Removed devices:", removed);
console.log("All devices:", devices);
};
meeting.self.on("deviceListUpdate", handleDeviceListUpdate);
return () => {
meeting.self.off("deviceListUpdate", handleDeviceListUpdate);
};
}, [meeting]);
meeting.addSelfEventListener(object : RtkSelfEventListener {
// Triggered when audio devices are added or removed
override fun onAudioDevicesUpdated() {
val audioDevices = meeting.localUser.getAudioDevices()
// Update UI with new audio device list
}
})
meeting.addSelfEventListener(object: RtkSelfEventListener {
// Triggered when audio devices are added or removed
func onAudioDevicesUpdated() {
let audioDevices = meeting.localUser.getAudioDevices()
// Update UI with new audio device list
}
})
useEffect(() => {
if (!meeting) return;
const handleDeviceListUpdate = ({ added, removed, devices }) => {
console.log("Device list updated");
console.log("Added devices:", added);
console.log("Removed devices:", removed);
console.log("All devices:", devices);
};
meeting.self.on("deviceListUpdate", handleDeviceListUpdate);
return () => {
meeting.self.off("deviceListUpdate", handleDeviceListUpdate);
};
}, [meeting]);
Network Quality Score
Monitor your own network quality:
meeting.self.on(
"mediaScoreUpdate",
({ kind, isScreenshare, score, scoreStats }) => {
if (kind === "video") {
console.log(
`Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
score,
);
}
if (kind === "audio") {
console.log("Your audio quality score is", score);
}
if (score < 5) {
console.log("Your media quality is poor");
}
},
);
The scoreStats object provides detailed statistics:
// Audio Producer
{
"kind": "audio",
"isScreenshare": false,
"score": 10,
"participantId": "meeting.self.id",
"scoreStats": {
"score": 10,
"bitrate": 22452,
"packetsLostPercentage": 0,
"jitter": 0,
"isScreenShare": false
}
}
// Video Producer
{
"kind": "video",
"isScreenshare": false,
"score": 10,
"participantId": "meeting.self.id",
"scoreStats": {
"score": 10,
"frameWidth": 640,
"frameHeight": 480,
"framesPerSecond": 24,
"jitter": 0,
"isScreenShare": false,
"packetsLostPercentage": 0,
"bitrate": 576195,
"cpuLimitations": false,
"bandwidthLimitations": false
}
}
useEffect(() => {
if (!meeting) return;
const handleMediaScoreUpdate = ({
kind,
isScreenshare,
score,
scoreStats,
}) => {
if (kind === "video") {
console.log(
`Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
score,
);
}
if (score < 5) {
console.log("Your media quality is poor");
}
};
meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
return () => {
meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
};
}, [meeting]);
Android SDK does not currently expose network quality scores.
iOS SDK does not currently expose network quality scores.
useEffect(() => {
if (!meeting) return;
const handleMediaScoreUpdate = ({
kind,
isScreenshare,
score,
scoreStats,
}) => {
if (kind === "video") {
console.log(
`Your ${isScreenshare ? "screenshare" : "video"} quality score is`,
score,
);
}
if (score < 5) {
console.log("Your media quality is poor");
}
};
meeting.self.on("mediaScoreUpdate", handleMediaScoreUpdate);
return () => {
meeting.self.off("mediaScoreUpdate", handleMediaScoreUpdate);
};
}, [meeting]);
Permission Updates
Triggered when permissions are updated dynamically:
// Listen to specific permission updates
meeting.self.permissions.on("chatUpdate", () => {
console.log("Chat permissions updated");
// Check meeting.self.permissions for updated permissions
});
meeting.self.permissions.on("pollsUpdate", () => {
console.log("Polls permissions updated");
});
meeting.self.permissions.on("pluginsUpdate", () => {
console.log("Plugins permissions updated");
});
// Listen to all permission updates
meeting.self.permissions.on("*", () => {
console.log("Permissions updated");
});
Monitor permissions using selectors:
const permissions = useRealtimeKitSelector((m) => m.self.permissions);
useEffect(() => {
console.log("Permissions updated:", permissions);
}, [permissions]);
Android SDK uses a different permissions model. Refer to the Android-specific documentation.
iOS SDK uses a different permissions model. Refer to the iOS-specific documentation.
Monitor permissions using selectors:
const permissions = useRealtimeKitSelector((m) => m.self.permissions);
useEffect(() => {
console.log("Permissions updated:", permissions);
}, [permissions]);
Media Permission Errors
Triggered when media permissions are denied or media capture fails:
meeting.self.on("mediaPermissionError", ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
// Handle different error types
if (message === "DENIED") {
console.log("User denied permission");
} else if (message === "SYSTEM_DENIED") {
console.log("System denied permission");
} else if (message === "COULD_NOT_START") {
console.log("Failed to start media stream");
}
});
Possible values:
message:'DENIED','SYSTEM_DENIED','COULD_NOT_START'kind:'audio','video','screenshare'
useEffect(() => {
if (!meeting) return;
const handlePermissionError = ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
if (message === "DENIED") {
// Show UI to guide user to grant permissions
}
};
meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onMeetingRoomJoinedWithoutCameraPermission() {
// meeting joined without camera permission
}
override fun onMeetingRoomJoinedWithoutMicPermission() {
// meeting joined without microphone permission
}
})
meeting.addSelfEventListener(self)
extension MeetingViewModel: RtkSelfEventListener {
func onMeetingRoomJoinedWithoutCameraPermission() {
// meeting joined without camera permission
}
func onMeetingRoomJoinedWithoutMicPermission() {
// meeting joined without microphone permission
}
}
You can also check permission status using properties:
let hasCameraPermission = meeting.localUser.isCameraPermissionGranted
let hasMicPermission = meeting.localUser.isMicrophonePermissionGranted
useEffect(() => {
if (!meeting) return;
const handlePermissionError = ({ message, kind }) => {
console.log(`Failed to capture ${kind}: ${message}`);
if (message === "DENIED") {
// Show UI to guide user to grant permissions
}
};
meeting.self.on("mediaPermissionError", handlePermissionError);
return () => {
meeting.self.off("mediaPermissionError", handlePermissionError);
};
}, [meeting]);
Waitlist Status
For meetings with waiting room enabled:
Monitor the roomState property for waitlist status. The value 'waitlisted' indicates the user is in the waiting room.
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
if (roomState === "waitlisted") {
console.log("Waiting for host to admit you");
}
}, [roomState]);
// Get current waitlist status
val waitListStatus = meeting.localUser.waitListStatus
// Listen to waitlist status changes
meeting.addSelfEventListener(object : RtkSelfEventListener {
override fun onWaitListStatusUpdate(waitListStatus: WaitListStatus) {
// handle waitlist status here
}
})
// Get current waitlist status
let waitListStatus = meeting.localUser.waitListStatus
// Listen to waitlist status changes
extension MeetingViewModel: RtkSelfEventListener {
func onWaitlistedUpdate() {
// handle waitlist update
}
}
const roomState = useRealtimeKitSelector((m) => m.self.roomState);
useEffect(() => {
if (roomState === "waitlisted") {
console.log("Waiting for host to admit you");
}
}, [roomState]);
iOS-Specific Events
The iOS SDK provides additional platform-specific events:
Proximity Sensor
Triggered when the proximity sensor detects a change (useful for earpiece detection):
extension MeetingViewModel: RtkSelfEventListener {
func onProximityChanged() {
// Handle proximity sensor change
// Useful for detecting when device is near user's ear
}
}
Webinar Events
For webinar-specific functionality:
extension MeetingViewModel: RtkSelfEventListener {
func onWebinarPresentRequestReceived() {
// Handle request to present in webinar
}
func onStoppedPresenting() {
// Handle stopped presenting in webinar
}
}
Room Messages
Listen to broadcast messages in the room:
extension MeetingViewModel: RtkSelfEventListener {
func onRoomMessage() {
// Handle room broadcast message
}
}
Pin and Unpin
Pin or unpin yourself in the meeting (requires appropriate permissions):
Web SDK does not currently support pinning the local participant.
Web SDK does not currently support pinning the local participant.
Android SDK does not currently support pinning the local participant.
// Pin yourself
meeting.localUser.pin()
// Unpin yourself
meeting.localUser.unpin()
// Check if pinned
let isPinned = meeting.localUser.isPinned
// Pin yourself
await meeting.self.pin();
// Unpin yourself
await meeting.self.unpin();
// Check if pinned
const isPinned = meeting.self.isPinned;
Update Media Constraints
Update video or screenshare resolution at runtime:
Web SDK does not currently expose runtime constraint updates for local participant.
Web SDK does not currently expose runtime constraint updates for local participant.
Android SDK does not currently expose runtime constraint updates.
iOS SDK does not currently expose runtime constraint updates.
Update Video Constraints
Update camera resolution while already streaming:
meeting.self.updateVideoConstraints({
width: { ideal: 1920 },
height: { ideal: 1080 },
});
Update Screenshare Constraints
Update screenshare resolution while already streaming:
meeting.self.updateScreenshareConstraints({
width: { ideal: 1920 },
height: { ideal: 1080 },
});
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/realtime/realtimekit/humans/core/local-participant/#page","headline":"Local Participant","description":"Manage local user media devices, audio, video, and screenshare in RealtimeKit meetings.","url":"https://developers.cloudflare.com/realtime/realtimekit/humans/core/local-participant/","inLanguage":"en","image":"https://developers.cloudflare.com/realtime/realtimekit/humans/core/local-participant/og.png?v=56c759aeac12b2a9","dateModified":"2026-10-19","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Use RealtimeKit host controls to mute, pin, or remove participants in a live session. title: Manage Participants in a Session image: https://developers.cloudflare.com/realtime/realtimekit/humans/core/manage-participants-in-a-session/og.png?v=e51984af50731733
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/realtime/llms.txt
Use this file to discover all available pages before exploring further.
Docs