Every call screen has camera and mic buttons. This page shows how to turn the camera and microphone on and off, show a preview before joining, and handle the errors users actually hit (like "camera blocked").
One call does it all. Turning on a device captures it and
publishes it (sends it to everyone in the room). There's no separate
publish() step for the normal case.
Before you start:
- Your app has joined a room. See Quickstart.
- The token has
publish: true. Your backend sets this; see Authentication. Without it, the server refuses the camera and the room fires anerrorevent.
All code on this page runs in your app.
What happens when you turn on the camera
- The SDK asks the browser or phone for the camera. The first time, the user sees a permission prompt (see Permissions).
- The camera light turns on, and the SDK creates a local track.
- The SDK publishes the track to the room.
- Everyone else gets
trackSubscribedand can show your video. - Your app gets
localTrackPublished.
Turning the camera off does the reverse: it stops sending, and releases the camera so the light turns off.
Turn devices on and off
await room.enableCamera(); // captures and publishes in one call
await room.enableMicrophone();
await room.disableCamera(); // stops publishing and releases the device
await room.disableMicrophone();'use client';
import { useCamera, useMicrophone } from '@livqeno/react';
function DeviceControls() {
const camera = useCamera();
const microphone = useMicrophone();
return (
<>
<button onClick={() => (camera.enabled ? camera.disable() : camera.enable())} disabled={camera.busy}>
Camera: {camera.enabled ? 'on' : 'off'}
</button>
<button onClick={() => (microphone.enabled ? microphone.disable() : microphone.enable())}>
Mic: {microphone.enabled ? 'on' : 'off'}
</button>
{camera.error && <p role="alert">{camera.error.code}</p>}
</>
);
}Each hook gives you { enabled, track, enable(), disable(), error }.
There's no busy flag on web. Only React Native and Flutter have it,
because phones are slower to start the camera.
import { useCamera, useMicrophone } from '@livqeno/react-native';
function DeviceControls({ room }) {
const camera = useCamera(room); // { enabled, busy, enable(), disable(), toggle(), error }
const microphone = useMicrophone(room);
return (
<>
<Button onPress={camera.toggle} disabled={camera.busy} title={camera.enabled ? 'Camera on' : 'Camera off'} />
<Button onPress={microphone.toggle} title={microphone.enabled ? 'Mic on' : 'Mic off'} />
</>
);
}busy is true while the camera is starting. Disable the button while
it's true, so a double tap doesn't cause problems.
await room.enableCamera();
await room.disableCamera();
await room.enableMicrophone();
await room.disableMicrophone();Preview the camera before joining
Many apps show a "green room" screen: you see yourself, check your hair and lighting, then click Join. To build it, create the track first, show it, then publish it later.
Browser only. On React Native and Flutter, enableCamera() always captures
and publishes together.
const track = await client.createCameraTrack();
// show a preview, let the user confirm...
await room.publish(track);client.createMicrophoneTrack()works the same way for the mic.room.unpublish(track)stops sending the track. It doesn't change the on/off state thatenableCamera()/disableCamera()manage.
Handle errors
Turning on a device can fail for reasons outside your code: the user said "no" to the prompt, no camera is plugged in, or another app is using it. Plan a friendly message for each.
Camera and mic problems always throw a typed Livqeno error, never a raw platform error.
try {
await room.enableCamera();
} catch (error) {
if (error.code === 'CAMERA_PERMISSION_DENIED') {
// show your own "please allow camera access" UI
} else if (error.code === 'DEVICE_NOT_FOUND') {
// no camera attached
}
}import { isLivqenoPermissionError } from '@livqeno/react-native';
try {
await room.enableCamera();
} catch (error) {
if (isLivqenoPermissionError(error)) {
if (error.requiresSettings) {
// "blocked" — re-prompting does nothing; send the user to Settings
} else {
// "denied" — a normal prompt may still work next time
}
}
}LivqenoPermissionError is also an RTCError, so if (isRTCError(e)) code
still works. See Permissions.
try {
await room.enableCamera();
} on LivqenoPermissionException catch (e) {
if (e.permanentlyDenied) {
// send the user to system settings
}
} on LivqenoException catch (e) {
if (e.code == LivqenoErrorCode.deviceNotFound) {
// no camera attached
}
}Muting vs. unpublishing
There are two ways to "turn off" a track, and users see them differently. Pick the one that matches what the button means.
track.mute() | room.unpublish(track) | |
|---|---|---|
| What happens | Stops sending media. The track stays in the room. | Removes the track from the room. |
| Others see | A muted icon (trackMuted / trackUnmuted). | The track is gone until you publish again. |
| Use it for | A normal mute button. | Turning a feature off, like ending a screen share. |
Good to know
- Turning on a device that's already on does nothing. Turning off a device that's already off does nothing. Neither throws.
trackMuted,trackUnmuted,trackSubscribed, andtrackUnsubscribedwork the same on every platform. It doesn't matter which SDK the other person uses.
Tips for production
- Ask for camera and mic access when the user joins a call, not when the app opens. People say "no" much more often to a prompt without context.
- On React Native and Flutter, "denied" and "blocked" need different screens. See Permissions.
- Don't poll device state. Listen to
trackMuted,trackUnmuted,localTrackPublished, andlocalTrackUnpublishedinstead.
Next steps
Your users can now send audio and video. Next:
- Permissions: handle the camera/mic prompt well on each platform.
- Rooms & Participants: show everyone's video.
Related:
- Screen Sharing: works the same way (enable / disable).
- Permissions: camera and mic access on each platform.
- Diagnostics: resolution, framerate, and bitrate of what you send.