livqenoDocs

RTCPermissions

Permissions

Camera and microphone access — how each platform asks, and how to handle "no".

Before a user can share their camera or mic, the browser or phone asks for permission ("Allow this site to use your camera?"). The OS shows the prompt, not Livqeno, so each platform works a little differently. This page covers Web, React Native, and Flutter.

Why it matters: if a user says "no", or the prompt never appears, your call shows a black tile or silence, often with no obvious error. Handling permissions well is the difference between "it just works" and a support ticket.

Two kinds of permission, don't mix them up:

Who decidesWhere it's set
Device permission (this page)The user, in the OS promptBrowser / phone settings
Token permission (publish, subscribe, ...)Your backendWhen it makes the token. See Authentication.

A user needs both to send video: your token must allow publish, and the user must allow the camera.

All code on this page runs in your app. The manifest/plist setup at the end goes in your mobile project's config files.

Web

The browser shows the prompt the first time you call enableCamera() or enableMicrophone(). There's nothing to call ahead of time. After the user says "yes", the browser remembers it for your site.

try {
  await room.enableCamera();
} catch (error) {
  if (error.code === 'CAMERA_PERMISSION_DENIED') {
    // show your own "please allow camera access" UI
  }
}

The browser doesn't tell you if the user said "no" once or blocked it for good. Just tell the user: "Check your browser's camera permission for this site."

React Native

Phones give you more control than browsers: you can check the status without a prompt (on Android), and tell "denied once" apart from "blocked". React Native has a full permissions module:

import { permissions } from '@livqeno/react-native';
 
const status = await permissions.check();
// { camera: 'granted'|'denied'|'blocked'|'undetermined'|'unavailable', microphone: ... }
 
const result = await permissions.request(['camera', 'microphone']); // prompts; never throws
await permissions.require(['camera']); // prompts; throws LivqenoPermissionError on anything but granted
MethodShows a prompt?Throws?
check()NoNo
request()YesNever
require()YesYes, unless granted

How the platforms differ:

  • Android: check() tells you the real status without a prompt.
  • iOS: check() always says undetermined, because iOS doesn't let JavaScript read the status. Use request() to find out.

When permission is blocked

blocked means the user said "no" for good (Android's "Don't ask again", or any iOS denial after the first one). Asking again shows nothing.

In this case, LivqenoPermissionError.requiresSettings is true. Send the user to the Settings app:

import { permissions, isLivqenoPermissionError } from '@livqeno/react-native';
 
try {
  await permissions.require(['camera']);
} catch (error) {
  if (isLivqenoPermissionError(error) && error.requiresSettings) {
    // open Settings — prompting again shows no dialog on this platform
  }
}

Automatic prompt on join

livqeno.join() calls permissions.request() for you. It doesn't throw, so a user who says "no" to the camera can still join and listen.

Have your own permission screen? Pass { requestPermissions: false } to turn this off.

Flutter

final granted = await LivqenoPermissions.request();
// { LivqenoPermission.camera: true, LivqenoPermission.microphone: false }
 
await LivqenoPermissions.require(); // throws LivqenoPermissionException
  • request() shows the OS prompt.
  • Livqeno can't check the status without a prompt, and can't tell "denied" from "denied for good".

Need either of those? Add the permission_handler package alongside Livqeno:

final status = await Permission.camera.status;
if (status.isPermanentlyDenied) {
  await openAppSettings();
} else {
  await LivqenoPermissions.require([LivqenoPermission.camera]);
}

Platform manifest/plist setup

You must declare the permissions in your app's config. Livqeno can't do this for you, and nothing above works without it.

iOS (React Native and Flutter), ios/*/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is used for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is used for calls.</string>

Android (React Native and Flutter), android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

Common errors

ErrorPlatformCauseFix
The app crashes (no error)React Native, FlutterAn iOS usage string is missing from Info.plist.Add both NSCameraUsageDescription and NSMicrophoneUsageDescription.
CAMERA_PERMISSION_DENIED / MICROPHONE_PERMISSION_DENIEDWebThe user said "no" to the browser prompt.Show your own explanation. The browser controls asking again.
LivqenoPermissionError with requiresSettings: trueReact NativeBlocked for good.Send the user to system Settings. Asking again shows nothing.
LivqenoPermissionException (permanentlyDenied: false)FlutterLivqeno can't tell if the "no" is permanent.Add permission_handler if you need to know (see above).

Tips for production

  • Ask when the user joins a call, and explain why in your own UI first. People say "no" much more often to a prompt with no context.
  • Don't block joining if the user says "no". Let them join as a listener.
  • Test the "blocked for good" case on a real device. Simulators don't always behave the same.

Next steps