---
name: expo-av-volume-cap-and-gain
description: Handle "boost volume / increase gain" requests in Expo/React Native voice features without silently falling short of user intent.
---
# Expo-AV Volume Cap and Gain Boost

## When to use
- User asks to "boost", "amplify", "make louder", or "raise gain" on audio playback in an Expo / React Native app (mobile voice page, TTS playback, call audio).
- Task instructions mention `GainNode` or Web Audio API for a feature that currently uses `expo-av` (`Audio.Sound`, `Audio.Recording`).
- A speaker/earpiece toggle needs to *meaningfully* change perceived loudness, not just route audio.

## How to apply
1. Confirm the playback path. Grep for `Audio.Sound`, `createAsync`, `setVolumeAsync`, `playThroughEarpieceAndroid` to identify whether playback is `expo-av` or a WebView/native module.
2. If `expo-av`: remember `volume` is clamped to `[0.0, 1.0]`. You cannot exceed unity gain natively. Don't pretend a GainNode will work — it won't run outside a WebView/JSI bridge.
3. Implement the toggle by:
   - Defining named constants (e.g. `SPEAKER_VOLUME = 1.0`, `EARPIECE_VOLUME = 0.35`) in the voice service, not magic numbers in the component.
   - Adding a `getPlaybackVolume()` accessor on the service.
   - Emitting a `volumeChanged` event from the service *before* the existing `speakerChanged` event so subscribers update gain before route flips.
   - Passing `volume: service.getPlaybackVolume()` into every `Audio.Sound.createAsync(...)` so the first chunk plays at the right gain.
   - Subscribing in the screen via `useEffect` and calling `soundRef.current.setVolumeAsync(vol)` on `volumeChanged` so mid-utterance toggles take effect immediately.
4. On Android, keep `playThroughEarpieceAndroid` route flipping — combined with the 2–3× linear gain ratio it produces an audibly significant on/off difference.
5. If the user *truly* needs >1.0 gain (analog boost beyond device max), document that it requires routing TTS through a WebView with `AudioContext` + `GainNode`, leave a code comment explaining the architectural cost, and surface the tradeoff to the user instead of silently shipping a weaker fix.
6. Flag explicitly that you cannot smoke-test mobile audio from the agent environment (Expo Go / native LiveKit modules don't run headless). Ask the user to confirm "audibly louder" and "no clipping" on a real device.

## Rules
- Never claim a Web Audio `GainNode` solution works in an `expo-av`-backed screen without a WebView — `expo-av` clamps `volume` to 1.0.
- Never hardcode volume values inside component handlers; put them in the voice service as named constants so both initial play and toggle reads share one source of truth.
- Always emit a `volumeChanged` event before `speakerChanged` so listeners apply gain before route changes, avoiding a one-frame loudness dip.
- Always pass the current volume into the *initial* `createAsync` options — `setVolumeAsync` after the fact still lets the first chunk play at the default level.
- Always state explicitly when a mobile audio change is unverified because Expo Go / native modules can't run in the agent environment; ask the user to confirm on device.
- Never silently ship a workaround that under-delivers vs. the literal ask (e.g. "use GainNode") — name the architectural limit and the alternative path.