Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Audio Recorder

AudioRecorder is the recorder that MessageComposer shows in place of its input while a voice recording runs. StartRecordingAudioButton starts the recording. Both appear only on a composer with audioRecordingEnabled. Voice recordings and audio playback explains how a recording is made and sent.

import { MessageComposer } from "stream-chat-react";

export const ComposerWithRecording = () => (
  <MessageComposer audioRecordingEnabled />
);

Best practices

  • Turn recording on for each composer that should have it. A thread's MessageComposer needs audioRecordingEnabled too.
  • Declare audioRecordingConfig once, at module scope or in useMemo. A new object on every render creates a new recorder on every render.
  • Replace the recorder's parts through their override keys. A part of your own reads the recorder from recordingController in useMessageComposerContext().
  • Forward the ref in a StartRecordingAudioButton of your own: the permission callout is anchored to it.
  • Serve the app over HTTPS. Browsers offer navigator.mediaDevices only on HTTPS and localhost, and without it the record button doesn't show.

UI customization

Override key Replaces Props
StartRecordingAudioButton The record button StartRecordingAudioButtonProps, the button props
AudioRecorder The recorder, while a recording runs none
RecordingPermissionDeniedNotification The callout shown when the microphone is blocked RecordingPermissionDeniedNotificationProps
VoiceRecordingPreviewSlot A finished recording waiting to be sent, with asyncMessagesMultiSendEnabled see Composer UI components

The composer shows the record button in place of the send button while the composer is empty, nothing is being edited, no command is active, no slow mode cooldown runs and the browser can record. The button gets onClick and disabled: it's disabled while a recording runs and, without asyncMessagesMultiSendEnabled, while the composer already holds a voice recording.

The default AudioRecorder shows a live waveform and a timer while recording, and a player for what was recorded while it's paused. Its buttons pause and resume, and complete the recording. While it's paused, a delete button cancels the recording and publishes a "Voice message deleted" notification.

import { forwardRef } from "react";
import type { StartRecordingAudioButtonProps } from "stream-chat-react";
import { MessageComposer, WithComponents } from "stream-chat-react";

const RecordButton = forwardRef<
  HTMLButtonElement,
  StartRecordingAudioButtonProps
>(function RecordButton(props, ref) {
  return (
    <button {...props} className="record-button" ref={ref} type="button">
      Record
    </button>
  );
});

const overrides = { StartRecordingAudioButton: RecordButton };

export const ComposerWithRecordButton = () => (
  <WithComponents overrides={overrides}>
    <MessageComposer audioRecordingEnabled />
  </WithComponents>
);

The recorder replaces AudioRecorder with a recorder of its own.

Browser permissions

The first click on the record button starts the recorder, and the browser asks for the microphone. When access was denied, the click opens a callout anchored to the button that renders RecordingPermissionDeniedNotification: "Allow access to microphone" and how to do it.

Prop Type Default Description
permissionName RecordingPermission required The blocked permission: "microphone", or "camera". The composer passes "microphone".

The SDK's dialog layer closes the callout, so the notification gets no onClose. Its text comes from the translation keys under mediaRecorder.permissionDenied.

recordingController.permissionState holds the browser's answer, "granted", "denied" or "prompt", for a part that wants to show it before the click.

Custom encoding

audioRecordingConfig on MessageComposer configures the recorder. It's a CustomAudioRecordingConfig, with three parts, each optional:

Field Holds Default
mediaRecorderConfig The browser MediaRecorder options, such as mimeType mimeType: "audio/webm", or "audio/mp4;codecs=mp4a.40.2" where WebM can't be recorded
transcoderConfig sampleRate, and encoder: (file: File, sampleRate: number) => Promise<Blob>, which converts the recording sampleRate: 16000; the encoder produces WAV
amplitudeRecorderConfig The waveform: sampleCount, how many values are saved; samplingFrequencyMs, how often the level is read; analyserConfig, the fftSize, minDecibels and maxDecibels of the analyser sampleCount: 100; samplingFrequencyMs: 60; analyserConfig: { fftSize: 32, maxDecibels: 0, minDecibels: -100 }

A recording made as audio/mp4 isn't converted; every other recording goes through the encoder. To send MP3 instead of WAV, pass encodeToMp3 from stream-chat-react/mp3-encoder, which needs the @breezystack/lamejs package:

import type { CustomAudioRecordingConfig } from "stream-chat-react";
import { MessageComposer } from "stream-chat-react";
import { encodeToMp3 } from "stream-chat-react/mp3-encoder";

// Declared once: a new object on every render creates a new recorder.
const audioRecordingConfig: CustomAudioRecordingConfig = {
  amplitudeRecorderConfig: { sampleCount: 60 },
  transcoderConfig: { encoder: encodeToMp3 },
};

export const Mp3Composer = () => (
  <MessageComposer
    audioRecordingConfig={audioRecordingConfig}
    audioRecordingEnabled
  />
);

Encoding recordings as MP3 has the details.

Sending behavior

Completing a recording turns it into a voiceRecording attachment and uploads it with the composer's attachment manager. What happens next depends on asyncMessagesMultiSendEnabled on MessageComposer:

asyncMessagesMultiSendEnabled After the upload
false, the default The composer sends the message at once.
true The recording stays in the composer, shown by VoiceRecordingPreviewSlot, and goes out with the rest of the message when the user sends.

The attachment's fields are on Voice Recording Attachment.

Recording controller

Parts inside MessageComposer read the recorder from recordingController in useMessageComposerContext(). This badge shows while a recording runs:

import {
  MediaRecordingState,
  useMessageComposerContext,
} from "stream-chat-react";

export const RecordingBadge = () => {
  const { recordingController } = useMessageComposerContext();

  if (recordingController.recordingState !== MediaRecordingState.RECORDING) {
    return null;
  }

  return <span className="recording-badge">Recording…</span>;
};

recordingController is a RecordingController:

Field Type Description
recorder MediaRecorderController | undefined The recorder: the MediaRecorderController methods start(), pause(), resume(), stop() and cancel() control the recording. undefined without audioRecordingEnabled.
recordingState MediaRecordingState | undefined RECORDING, PAUSED or STOPPED, or undefined while nothing is being recorded.
recording LocalVoiceRecordingAttachment | undefined The recording as an attachment, once the recorder is paused or stopped.
permissionState PermissionState | undefined The browser's microphone permission.
completeRecording () => void Stops the recorder, uploads the recording and, without asyncMessagesMultiSendEnabled, sends it.

MediaRecordingState and RecordingPermission are exported enums, and RecordingController and CustomAudioRecordingConfig are exported types.

Hook Returns
useMessageComposerContext() the composer's props and recordingController; throws outside MessageComposer
useMessageComposerController() the composer the recording is added to: the channel's, the thread's or the edit composer

See useMessageComposerContext.