<!-- AvatarFactory Docs · https://avatarfactory.in/docs/sdk/react-native/permissions · Full map: https://avatarfactory.in/llms.txt -->

# Permissions

Microphone access for call mode, plus the optional camera — declared by the Expo plugin, requested at runtime by the SDK.

Only **`call`** mode needs the microphone. `tts`, `audio`, and `player` modes never touch it, so no permission is requested in those modes.

The **camera** is separate and entirely optional — it is only involved if you opt in with `perception: { camera: true }`.

## Microphone

### Declaring the permission

`@avatarfactory/expo-audio` ships an Expo config plugin that declares the microphone permission for you. Add it to `app.json` and run prebuild:

```json
{
  "expo": {
    "plugins": ["@avatarfactory/expo-audio"]
  }
}
```

```bash
npx expo prebuild
```

The plugin adds:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `iOS` | `NSMicrophoneUsageDescription` | — | Added to Info.plist so iOS can show the system microphone prompt. |
| `Android` | `RECORD_AUDIO` | — | Added to AndroidManifest.xml — required to capture mic audio. |
| `Android` | `MODIFY_AUDIO_SETTINGS` | — | Added to AndroidManifest.xml — required for the echo-cancelled full-duplex audio engine. |

> Want a custom iOS prompt string? Override `NSMicrophoneUsageDescription` in your `app.json` under `ios.infoPlist`. The system shows this text the first time the mic is requested.

### Runtime flow

When `start()` is called in `call` mode, the SDK calls `AvatarAudio.requestPermissionsAsync()` before opening the mic:

- **Granted** — the session connects and mic streaming begins.
- **Denied** — the SDK surfaces an alert offering to open Settings, then emits a fatal `MIC_PERMISSION_DENIED` error and stops the session. It is the one fatal code with `retryable: false`: don't offer a Retry button, send them to Settings.

You don't have to wire any of this up — but you should handle the error to guide the user.

```tsx
import { Text, Button } from "react-native";
import { useAvatar } from "@avatarfactory/react-native";

function MicGate() {
  const { error, start } = useAvatar();

  if (error?.code === "MIC_PERMISSION_DENIED") {
    return (
      <>
        <Text>🎤 Microphone access is blocked.</Text>
        <Text>Enable it in Settings, then try again.</Text>
        <Button title="Try again" onPress={start} />
      </>
    );
  }

  return null;
}
```

### Requesting permission yourself

To check or pre-request the microphone before starting a session — for example, on a "Start call" screen — call the module directly:

```ts
import { AvatarAudio } from "@avatarfactory/expo-audio";

async function ensureMic() {
  const { granted } = await AvatarAudio.requestPermissionsAsync();
  return granted;
}
```

> Requesting permission ahead of time lets you show your own rationale screen before the system prompt appears — often a smoother first-run experience than letting the prompt fire mid-connect.

## Camera

Only needed if you enable [camera perception](/docs/sdk/react/perception). The
camera is off by default, and even when enabled the SDK opens it **just-in-time**
— when the avatar actually needs to look at something, never on connect.

### Declaring the permission

Camera support comes from `expo-camera`, a peer dependency. Add its config
plugin so the platform permission strings are declared:

```json
{
  "expo": {
    "plugins": [
      "@avatarfactory/expo-audio",
      [
        "expo-camera",
        { "cameraPermission": "Lets the avatar see what you show it." }
      ]
    ]
  }
}
```

```bash
npx expo prebuild
```

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `iOS` | `NSCameraUsageDescription` | — | Added to Info.plist. Write the string yourself — the system shows it verbatim, and \"needs camera access\" tells the user nothing. |
| `Android` | `CAMERA` | — | Added to AndroidManifest.xml. |

### Runtime flow

When the server asks for a look, the SDK checks the current camera permission
and requests it if it is not already held. Outcomes:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `granted` | `PerceptionOutcome` | — | The lens comes up and the self-view mounts. Bringing the camera up takes no picture — frames are only captured when a look is actually requested. |
| `denied` | `PerceptionOutcome` | — | The user refused. Nothing is captured and the call continues normally — a refused camera is never fatal. |
| `unavailable` | `PerceptionOutcome` | — | No camera, or the permission API errored. Retrying will not help. |

Permission is remembered once granted, so switching video off and on again — or
starting a later session — never re-prompts.

> The SDK deliberately asks again even when Android reports `canAskAgain: false`.
>   An expired "Only this time" grant reports that flag yet still prompts, and when
>   the denial really is permanent the OS simply refuses without showing a dialog —
>   so asking costs nothing and recovers a case that would otherwise be stuck.

> The system prompt asks for the camera; it does not explain why your avatar
>   wants it. Show your own rationale first, and leave the self-view visible — it
>   is the user's only continuous signal that the camera is on.

## Echo cancellation

Echo cancellation is **always on** during a call, on both iOS and Android. It lets the avatar speak through the device speaker while the microphone stays open, without the avatar hearing — and responding to — its own voice. There's nothing to configure; it just works.
