# Audio recorder

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

The audio recorder is the part of `MessageComposer` that records voice messages. The record button, `AudioRecordingButton`, replaces the send button while the composer is empty, and the recorder parts take the input's place while a recording runs. All of them appear only when `audioRecordingEnabled` is set on the `Channel` or `Thread`. [Voice recordings and audio playback](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/voice-recordings-and-audio-playback/#recording) explains how a recording is made and sent.

```tsx title="src/ChannelScreen.tsx"
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
} from "stream-chat-react-native";

export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => (
  <Channel audioRecordingEnabled channel={channel}>
    <MessageList />
    <MessageComposer />
  </Channel>
);
```

## Best practices

- Turn recording on for each `Channel` and `Thread` that should have it. A thread screen needs `audioRecordingEnabled` on its `Thread` too.
- Install an audio package and allow uploads in the channel. The record button checks neither; see [Turning it on or off](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/voice-recordings-and-audio-playback/#turning-it-on-or-off).
- Set the gesture distances and the press duration on `Channel` or `Thread`. The record button reads them from `MessageInputContext`, and the same props on `MessageComposer` don't reach it.
- Replace the parts through their override keys. A part of your own reads the recorder from `audioRecorderManager` in `useMessageInputContext()`, and calls the context's recording functions.
- Keep the default record button unless you replace the whole gesture. Its press, slide-to-cancel and slide-to-lock gestures drive the other parts.

## UI customization

| Override key                   | Component                      | Replaces                                                                                                | Props                                                                                                                                       |
| ------------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `StartAudioRecordingButton`    | `AudioRecordingButton`         | The record button                                                                                       | [Below](#audiorecordingbutton-props)                                                                                                        |
| `AudioRecorder`                | `AudioRecorder`                | The input, while a recording runs: the time and "Slide to Cancel", or the buttons of a locked recording | [Below](#audiorecorder-props)                                                                                                               |
| `AudioRecordingLockIndicator`  | `AudioRecordingLockIndicator`  | The lock mark above the record button, while a recording runs                                           | [Below](#audiorecordinglockindicator-props)                                                                                                 |
| `AudioRecordingInProgress`     | `AudioRecordingInProgress`     | The time and the live waveform above the input, while a locked recording runs                           | [Below](#audiorecordinginprogress-props)                                                                                                    |
| `AudioRecordingWaveform`       | `AudioRecordingWaveform`       | The live waveform inside `AudioRecordingInProgress`                                                     | [Below](#audiorecordingwaveform-props)                                                                                                      |
| `AudioRecordingPreview`        | `AudioRecordingPreview`        | The player above the input, after a locked recording is stopped                                         | none                                                                                                                                        |
| `AudioAttachmentUploadPreview` | `AudioAttachmentUploadPreview` | A finished recording waiting in the composer                                                            | [Composer UI components](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/composer-ui-components/#upload-preview-props) |

What the composer shows during a recording:

1. **Holding the button.** `AudioRecorder` replaces the input with a red microphone, the elapsed time and "Slide to Cancel", which fades as the user slides to the left. `AudioRecordingLockIndicator` shows an open lock and an arrow above the button. The previews above the input and the attach button are hidden.
2. **Locked.** `AudioRecordingInProgress` shows the time and the waveform above the input, and `AudioRecorder` shows three buttons: delete, stop and send. The lock indicator shows a closed lock, then disappears after two seconds.
3. **Stopped.** `AudioRecordingPreview` plays the recording, with a play button, the time and a waveform the user can drag to seek. `AudioRecorder` shows delete and send.

The delete button publishes a "Voice message deleted" notification. The send button calls `uploadVoiceRecording(audioRecordingSendOnComplete)`, which adds it to the composer or sends it at once.

## AudioRecordingButton props

`AudioRecordingButton` reads its props from `MessageInputContext` and the recorder's state. Pass one to override it.

A press shorter than `asyncMessagesMinimumPressDuration` publishes "Hold to record. Release to save." A longer press pauses the audio that is playing and starts recording. When the microphone permission is denied, it publishes a notification with a button that opens the settings. While the user holds it, the button tracks the finger:

- Releasing it after sliding to the left by more than half of `asyncMessagesSlideToCancelDistance` deletes the recording.
- Releasing it after sliding up by more than half of `asyncMessagesLockDistance` locks the recording.
- Releasing it otherwise completes the recording with `uploadVoiceRecording(audioRecordingSendOnComplete)`, or deletes it when it's shorter than 300 milliseconds.

| Prop                                 | Type                                         | Default                             | Description                                                                                                     |
| ------------------------------------ | -------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `handlePress`                        | `() => void`                                 | none                                | Called on a short press, before the "Hold to record" notification, which still shows while nothing is recorded. |
| `handleLongPress`                    | `() => void`                                 | none                                | Called on a long press, in place of starting the recording.                                                     |
| `asyncMessagesMinimumPressDuration`  | `number`                                     | from `MessageInputContext`, `500`   | How long, in milliseconds, the user holds the button to start recording.                                        |
| `asyncMessagesSlideToCancelDistance` | `number`                                     | from `MessageInputContext`, `75`    | How far, in points, the finger slides to the left to cancel.                                                    |
| `asyncMessagesLockDistance`          | `number`                                     | from `MessageInputContext`, `50`    | How far, in points, the finger slides up to lock.                                                               |
| `audioRecordingSendOnComplete`       | `boolean`                                    | from `MessageInputContext`, `false` | Passed to `uploadVoiceRecording` when the user releases the button.                                             |
| `audioRecorderManager`               | `AudioRecorderManager`                       | from `MessageInputContext`          | The recorder whose `micLocked` the lock gesture sets.                                                           |
| `startVoiceRecording`                | `() => Promise<boolean \| undefined>`        | from `MessageInputContext`          | Starts recording on a long press.                                                                               |
| `deleteVoiceRecording`               | `() => Promise<void>`                        | from `MessageInputContext`          | Deletes the recording on cancel and on an early release.                                                        |
| `uploadVoiceRecording`               | `(sendOnComplete: boolean) => Promise<void>` | from `MessageInputContext`          | Completes the recording on release.                                                                             |
| `status`                             | `RecordingStatusStates`                      | the recorder's `status`             | `"idle"`, `"recording"` or `"stopped"`. Locking works and release completes only while it's `"recording"`.      |
| `recording`                          | `AudioRecordingReturnType`                   | the recorder's `recording`          | The recording in progress. While it's set, a short press shows no notification and a long press starts nothing. |
| `cancellableDuration`                | `boolean`                                    | whether `duration` is under 300 ms  | While it's `true`, releasing the button deletes the recording.                                                  |
| `buttonSize`                         | `number`                                     | none                                | Not read.                                                                                                       |

## AudioRecorder props

`AudioRecorder` reads its props from `MessageInputContext` and the recorder's state. `MessageComposer` passes it `slideToCancelStyle`.

| Prop                           | Type                                         | Default                    | Description                                                                                                    |
| ------------------------------ | -------------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `slideToCancelStyle`           | `StyleProp<AnimatedStyle<ViewStyle>>`        | none                       | The style of "Slide to Cancel". `MessageComposer` passes an animated opacity that fades it as the user slides. |
| `micLocked`                    | `boolean`                                    | the recorder's `micLocked` | Shows the buttons of a locked recording instead of the time and "Slide to Cancel".                             |
| `status`                       | `RecordingStatusStates`                      | the recorder's `status`    | While it's `"stopped"`, the buttons of a locked recording leave out the stop button.                           |
| `duration`                     | `number`                                     | the recorder's `duration`  | The elapsed time, in milliseconds, shown as `mm:ss`.                                                           |
| `audioRecordingSendOnComplete` | `boolean`                                    | from `MessageInputContext` | Passed to `uploadVoiceRecording` by the send button.                                                           |
| `deleteVoiceRecording`         | `() => Promise<void>`                        | from `MessageInputContext` | Called by the delete button.                                                                                   |
| `stopVoiceRecording`           | `() => Promise<void>`                        | from `MessageInputContext` | Called by the stop button.                                                                                     |
| `uploadVoiceRecording`         | `(sendOnComplete: boolean) => Promise<void>` | from `MessageInputContext` | Called by the send button.                                                                                     |
| `audioRecorderManager`         | `AudioRecorderManager`                       | from `MessageInputContext` | Not read.                                                                                                      |

## AudioRecordingLockIndicator props

`MessageComposer` renders `AudioRecordingLockIndicator` while a recording runs, with these props.

| Prop                 | Type                                  | Default  | Description                                                                                                           |
| -------------------- | ------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `micLocked`          | `boolean`                             | required | Shows a closed lock instead of an open lock and an arrow. The indicator disappears two seconds after it turns `true`. |
| `messageInputHeight` | `number`                              | none     | The composer's height. The indicator sits that far from the bottom.                                                   |
| `style`              | `StyleProp<AnimatedStyle<ViewStyle>>` | none     | `MessageComposer` passes an animated translation that moves the indicator up with the finger.                         |

## AudioRecordingInProgress props

`AudioRecordingInProgress` reads its props from `MessageInputContext` and the recorder's state. It renders `AudioRecordingWaveform` with `maxDataPointsDrawn` and `waveformData`.

| Prop                   | Type                   | Default                       | Description                                          |
| ---------------------- | ---------------------- | ----------------------------- | ---------------------------------------------------- |
| `maxDataPointsDrawn`   | `number`               | `60`                          | How many of the latest waveform samples are drawn.   |
| `duration`             | `number`               | the recorder's `duration`     | The elapsed time, in milliseconds, shown as `mm:ss`. |
| `waveformData`         | `number[]`             | the recorder's `waveformData` | The audio levels sampled so far, each from 0 to 1.   |
| `audioRecorderManager` | `AudioRecorderManager` | from `MessageInputContext`    | Not read.                                            |

## AudioRecordingWaveform props

| Prop                 | Type       | Default  | Description                                                               |
| -------------------- | ---------- | -------- | ------------------------------------------------------------------------- |
| `maxDataPointsDrawn` | `number`   | required | How many of the latest samples are drawn, one bar each.                   |
| `waveformData`       | `number[]` | required | The audio levels, each from 0 to 1. A bar's height is its level times 20. |

`AudioRecordingPreview` takes no props. It reads the recording, its duration and its waveform from the recorder, and plays it through the audio player pool of the `Channel` or `Thread`.

## The recorder

`audioRecorderManager` in `useMessageInputContext()` is the recorder of the `Channel` or `Thread`. Its `state` store holds:

| Field          | Type                                 | Description                                                                            |
| -------------- | ------------------------------------ | -------------------------------------------------------------------------------------- |
| `status`       | `"idle" \| "recording" \| "stopped"` | `"recording"` while it records, `"stopped"` after the stop button, `"idle"` otherwise. |
| `duration`     | `number`                             | The elapsed time, in milliseconds.                                                     |
| `waveformData` | `number[]`                           | The audio levels sampled so far, each from 0 to 1.                                     |
| `micLocked`    | `boolean`                            | Whether the recording is locked.                                                       |
| `isStarting`   | `boolean`                            | Whether a recording is being set up.                                                   |
| `recording`    | `AudioRecordingReturnType`           | The recording of the audio package, or its URI.                                        |

The functions that act on it, `startVoiceRecording()`, `stopVoiceRecording()`, `deleteVoiceRecording()` and `uploadVoiceRecording(sendOnComplete)`, are on [MessageInputContext](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/messageinputcontext/#values).

`MessageComposer` also provides `MicPositionContext`, which `useMicPositionContext()` reads. It holds `micPositionX` and `micPositionY`, Reanimated shared values with how far the finger has moved from the record button, in points. The record button writes them, and the composer animates "Slide to Cancel" and the lock indicator from them. A replacement for one of those parts can read them too.

## Configuration on Channel and Thread

| Prop                                 | Type      | Default | Description                                                                                         |
| ------------------------------------ | --------- | ------- | --------------------------------------------------------------------------------------------------- |
| `audioRecordingEnabled`              | `boolean` | `false` | Shows the record button while the composer has no text and no attachments.                          |
| `audioRecordingSendOnComplete`       | `boolean` | `false` | Sends a finished recording at once, as a message of its own, instead of keeping it in the composer. |
| `asyncMessagesMinimumPressDuration`  | `number`  | `500`   | How long, in milliseconds, the user holds the record button to start recording.                     |
| `asyncMessagesSlideToCancelDistance` | `number`  | `75`    | How far, in points, the user slides to the left to cancel.                                          |
| `asyncMessagesLockDistance`          | `number`  | `50`    | How far, in points, the user slides up to lock.                                                     |
| `allowConcurrentAudioPlayback`       | `boolean` | `false` | Lets several recordings play at once, including the preview of a stopped recording.                 |

`audioRecordingSendOnComplete` sends the recording before its upload has finished, so it needs `messageComposer.attachments.pendingUploadsEnabled`, which is off by default and which `Chat` turns on with `enableOfflineSupport`. The recording format comes from the audio package; to change it, pass `overrideAudioRecordingConfiguration` to `registerNativeHandlers()`, as [Entry points and native handlers](https://getstream.io/chat/docs/sdk/react-native/v10/reference/entry-points-and-native-handlers/) shows.

## Examples

### Show fewer waveform bars

The default `AudioRecordingInProgress` takes `maxDataPointsDrawn`. Register a component that renders it with the prop:

```tsx title="src/recorderOverrides.tsx"
import type { AudioRecordingInProgressProps } from "stream-chat-react-native";
import { AudioRecordingInProgress } from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";

const ShortWaveformInProgress = (props: AudioRecordingInProgressProps) => (
  <AudioRecordingInProgress {...props} maxDataPointsDrawn={30} />
);

// Pass to `WithComponents`, declared once at module scope.
export const recorderOverrides: ComponentOverrides = {
  AudioRecordingInProgress: ShortWaveformInProgress,
};
```

### Show that a recording runs

Parts under the `Channel` or `Thread` subscribe to the recorder's store. This badge shows while the user records:

```tsx title="src/RecordingBadge.tsx"
import { Text } from "react-native";
import {
  useMessageInputContext,
  useStateStore,
} from "stream-chat-react-native";

const recordingSelector = ({
  duration,
  status,
}: {
  duration: number;
  status: string;
}) => ({
  seconds: Math.floor(duration / 1000),
  isRecording: status === "recording",
});

export const RecordingBadge = () => {
  const { audioRecorderManager } = useMessageInputContext();
  const { isRecording, seconds } = useStateStore(
    audioRecorderManager.state,
    recordingSelector,
  );

  if (!isRecording) return null;

  return <Text>Recording {seconds}s</Text>;
};
```

[The recorder](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/voice-recordings-and-audio-playback/#the-recorder) replaces `AudioRecorder` with a recorder of its own.

## Hooks

| Hook                       | Returns                                                                                   |
| -------------------------- | ----------------------------------------------------------------------------------------- |
| `useMessageInputContext()` | `audioRecorderManager` and the recording functions; throws outside `Channel` and `Thread` |
| `useMicPositionContext()`  | `micPositionX` and `micPositionY`, inside `MessageComposer`                               |
| `useActiveAudioPlayer()`   | the audio player that is playing, or `null`                                               |
| `useAudioRecorder()`       | the recording functions for a recorder; `Channel` and `Thread` call it already            |

See [useMessageInputContext](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/composer-hooks/#usemessageinputcontext) and [useAudioRecorder](https://getstream.io/chat/docs/sdk/react-native/v10/reference/hooks/composer-hooks/#useaudiorecorder).

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/audio-recorder/](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/audio-recorder/).