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 }.
@avatarfactory/expo-audio ships an Expo config plugin that declares the microphone permission for you. Add it to app.json and run prebuild:
The plugin adds:
| Prop | Type | Default | Description |
|---|---|---|---|
iOSOptional | NSMicrophoneUsageDescription | No default | Added to Info.plist so iOS can show the system microphone prompt. |
AndroidOptional | RECORD_AUDIO | No default | Added to AndroidManifest.xml — required to capture mic audio. |
AndroidOptional | MODIFY_AUDIO_SETTINGS | No default | 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.
When start() is called in call mode, the SDK calls AvatarAudio.requestPermissionsAsync() before opening the mic:
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.
To check or pre-request the microphone before starting a session — for example, on a "Start call" screen — call the module directly:
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.
Only needed if you enable camera 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.
Camera support comes from expo-camera, a peer dependency. Add its config
plugin so the platform permission strings are declared:
| Prop | Type | Default | Description |
|---|---|---|---|
iOSOptional | NSCameraUsageDescription | No default | Added to Info.plist. Write the string yourself — the system shows it verbatim, and "needs camera access" tells the user nothing. |
AndroidOptional | CAMERA | No default | Added to AndroidManifest.xml. |
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 |
|---|---|---|---|
grantedOptional | PerceptionOutcome | No default | 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. |
deniedOptional | PerceptionOutcome | No default | The user refused. Nothing is captured and the call continues normally — a refused camera is never fatal. |
unavailableOptional | PerceptionOutcome | No default | 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 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.