> ## Documentation Index
> Fetch the complete documentation index at: https://docs.daily.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Build audio-only experiences with Daily

> Learn how to build voice-first and voice-only applications with Daily, including best practices and common feature patterns.

## Audio-only pricing

Accounts are automatically billed at the lower audio-only rate when no video tracks are present in a call. See our [pricing page](https://www.daily.co/pricing) for details.

<Note>
  If even one participant has an active video track at any point, the call will be billed as a video call.
</Note>

## Set up an audio-only room

The best way to enforce an audio-only experience is to configure room-level permissions to allow audio only. This way, even if a participant tries to turn on their camera, it won't work.

```bash theme={null}
curl --request POST \
     --url https://api.daily.co/v1/rooms \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{"properties": {"permissions": {"canSend": ["audio"]}}}'
```

Note that screen sharing is also blocked by this configuration, since screen captures are treated as video tracks.

## Best practices for audio quality

<Tip>
  These recommendations apply to working with audio in general, not just audio-only applications.
</Tip>

### Decouple `<audio>` elements from visual components

When `<audio>` tags are tied to visual elements (like an avatar), audio stops playing when the element scrolls out of view. Render `<audio>` elements separately from visual components:

```javascript theme={null}
return (
  <>
    <Container>
      <Header>Speakers</Header>
      {speakerTiles}
      <Header>Listeners</Header>
      {listenerTiles}
    </Container>
    <Audio participants={participants} />
  </>
);
```

### Offload expensive processing to an AudioWorklet

An [AudioWorklet](https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet) executes custom audio processing in a separate thread for low-latency processing. Daily Prebuilt uses this approach to detect microphone audio.

## Managing larger calls

For calls with more than \~5 participants, you need to actively manage audio to keep the experience usable. There are two complementary approaches: **receive-side** (limit which tracks each participant subscribes to) and **send-side** (limit who can unmute and speak).

### Receive-side: subscribe to active speakers only

By default, Daily subscribes every participant to every other participant's tracks. In a large audio-only call with many unmuted participants, this creates a cacophony of background noise and wastes bandwidth.

Disable automatic subscriptions and manage them manually:

```javascript theme={null}
const call = Daily.createCallObject({
  subscribeToTracksAutomatically: false,
});
```

Then subscribe only to the most recent active speakers using the [`active-speaker-change`](/reference/daily-js/events/participant-events#active-speaker-change) event:

```javascript theme={null}
const MAX_SPEAKERS = 5;
const activeSpeakers = [];

call.on('active-speaker-change', (event) => {
  const id = event.activeSpeaker?.peerId;
  if (activeSpeakers.includes(id)) return;

  call.updateParticipant(id, { setSubscribedTracks: { audio: true } });
  activeSpeakers.unshift(id);

  if (activeSpeakers.length > MAX_SPEAKERS) {
    const removed = activeSpeakers.pop();
    call.updateParticipant(removed, { setSubscribedTracks: { audio: false } });
  }
});
```

For more details, see the [track subscriptions guide](/docs/guides/scaling-calls/best-practices-to-scale-large-experiences#track-subscriptions).

If you're using Daily React, this behavior is built into the [`DailyAudio` component](/reference/daily-react/daily-audio):

```jsx theme={null}
import { DailyAudio } from '@daily-co/daily-react';

function CallComponent() {
  return <DailyAudio maxSpeakers={5} />;
}
```

### Send-side: control who can speak

For moderated experiences — webinars, Q\&As, stage-based calls — you'll want to limit how many participants can unmute at once. There are two approaches depending on whether participants should start muted or be promoted dynamically.

#### Start all participants as listeners

Set `canSend` to `False` at the room level so participants join as listeners by default, then grant audio permission individually:

```bash theme={null}
# Room where all participants join as listeners
curl --request POST \
     --url https://api.daily.co/v1/rooms \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{"properties": {"permissions": {"canSend": false}}}'
```

An owner can then promote a participant to speaker by calling [`updateParticipant()`](/reference/daily-js/instance-methods/update-participant) with updated permissions:

```javascript theme={null}
// Grant audio send permission to a specific participant
call.updateParticipant(participantSessionId, {
  updatePermissions: {
    canSend: new Set(['audio']),
  },
});

// Revoke it (mute them server-side)
call.updateParticipant(participantSessionId, {
  updatePermissions: {
    canSend: False,
  },
});
```

<Note>
  `updateParticipant()` with `updatePermissions` requires the caller to have `canAdmin: ['participants']` permission (i.e. they must be an owner or have been granted admin rights).
</Note>

#### Hand raising

Rather than polling or using app messages, use [`setUserData()`](/reference/daily-js/instance-methods/set-user-data) to broadcast raise/lower hand state. Each participant controls their own `userData`, and changes propagate to all participants via `participant-updated` events:

```javascript theme={null}
// Participant raises their hand
call.setUserData({ handRaised: true });

// Participant lowers their hand
call.setUserData({ handRaised: false });
```

Listen for `participant-updated` to update your UI when any participant's hand state changes:

```javascript theme={null}
call.on('participant-updated', (event) => {
  const { participant } = event;
  if (participant.userData?.handRaised) {
    // show raised hand indicator for this participant
  }
});
```

An owner can then call `updateParticipant()` to grant the raised-hand participant `canSend: ['audio']`.

## Analyze performance issues

Audio-only calls require relatively low bandwidth and CPU compared to video calls. If you see CPU or stale WebSocket issues, check for other causes — complex CSS animations can have a noticeable impact.

* Use the [`/logs` endpoint](/reference/rest-api/logs) after a session to analyze data.
* Use [`getNetworkStats()`](/reference/daily-js/instance-methods/get-network-stats) to get live audio bitrates and packet loss during a call.
