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
MessageComposerneedsaudioRecordingEnabledtoo. - Declare
audioRecordingConfigonce, at module scope or inuseMemo. 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
recordingControllerinuseMessageComposerContext(). - Forward the
refin aStartRecordingAudioButtonof your own: the permission callout is anchored to it. - Serve the app over HTTPS. Browsers offer
navigator.mediaDevicesonly on HTTPS andlocalhost, 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.
Related hooks
| 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 |