React
Six States Every Browser Audio Recorder Should Handle
By Vivek Jalondhara · 5 min read
Published
Model idle, permission request, recording, finalization, ready audio, and failure so a browser recorder offers the right action at each stage.
A boolean named isRecording cannot explain whether the browser is waiting for microphone access, collecting audio, finishing the final chunk, or holding a completed recording. Those situations expose different actions and require different cleanup.
This educational Ecoscript example defines six product states for a local browser recorder. Uploading, transcription, and summary review belong to later workflows. The model extends the portfolio’s recording-interface domain without claiming that these exact states or transitions are used in the project.
1. Idle: ready for an intentional start
Idle means no active capture attempt owns a stream. Show the recording purpose and a clear start action. Check whether the required browser capabilities are available before inviting the person into a workflow that cannot start. Unsupported environments can enter a failure state with an appropriate explanation.
Do not treat returning to idle as permission to discard an existing recording silently. If a completed recording is being replaced, resolve that decision first. The controller should create a fresh attempt identity so delayed events from an earlier session cannot become part of the new one.
2. Requesting access: waiting without pretending to record
The interface has requested a microphone stream but has not yet started the recorder. Display a specific waiting message and prevent duplicate requests. Permission can remain unanswered, so this state should have a meaningful exit path rather than an endlessly disabled page.
Leaving the workflow invalidates the attempt. A stream delivered afterward must be released instead of adopted. The separate microphone-permissions article develops that ownership problem in detail. Here, the state’s main responsibility is to distinguish requesting capture from actually producing recorded data.
3. Recording: own the active capture resources
Enter recording only when the controller has a usable stream and has successfully started recording. Present an obvious stop action and truthful elapsed-time feedback. If pause is supported, model it as an explicit mode within the recording state or extend the state machine deliberately.
The product state is broader than MediaRecorder.state, which describes the browser recorder’s own lifecycle. Keep the recorder and stream under one owner, handle unexpected capture interruption, and decide what happens if the person navigates away. A red icon alone does not establish that usable audio is reaching the recorder.
4. Finalizing: wait for the last data
After the user requests a stop, prevent another stop action and explain that the recording is being prepared. MediaRecorder delivers recorded data through dataavailable events; the stop event follows the final dataavailable event. Do not build the final Blob immediately after calling stop and assume all data has arrived.
Collect chunks for the current attempt, then finalize after the appropriate completion event. Empty output needs an explicit outcome. Also handle recorder errors independently: a normal stop sequence and an interrupted capture should not be presented as equally successful without checking what remains usable.
5. Ready: a local recording that can be reviewed
Ready means the local audio artefact exists and the interface can offer review, discard, or the next processing action. It does not mean that an upload succeeded or that a transcript is available. Keep those later acknowledgements visible in their own workflow.
Use the recorder’s actual output type rather than assuming every supported browser produces the same format. A nonempty Blob is only a basic integrity check, not proof of intelligible audio. Provide a playback path, release obsolete object URLs, and decide where the artefact lives if the review component unmounts.
6. Error: preserve what can be recovered
An error needs a stage and a useful next action. Failing before capture may permit another access attempt. Failing while recording requires a decision about any collected chunks. Failing during later upload should retain a usable local recording when the product’s lifetime policy permits it.
Keep error transitions in the same event model as ordinary transitions. A pure reducer can reject stale completion events and make unexpected ordering testable. Verify denial, interruption, empty final output, repeated stop actions, and starting a new attempt before an older callback arrives. Browser integration checks remain necessary even when those transition tests pass.
Accept final audio only for the current finalizing attempt
This is one transition from the six-state model. Invoke it after collecting the current attempt’s final data. Other events, resource cleanup, playback validation, and browser integration are deliberately handled by the recording controller.
Illustrative example · TypeScript · Simplified for explanation
type CaptureState =
| { phase: "idle" }
| { phase: "requesting" | "recording" | "finalizing"; attemptId: number }
| { phase: "ready"; attemptId: number; blob: Blob }
| { phase: "error"; stage: "access" | "capture" | "finalization"; message: string };
function finishCapture(
state: CaptureState, attemptId: number, blob: Blob
): CaptureState {
if (state.phase !== "finalizing" || state.attemptId !== attemptId) {
return state;
}
if (blob.size === 0) {
return { phase: "error", stage: "finalization", message: "No audio was captured" };
}
return { phase: "ready", attemptId, blob };
}- Request access for an identified attempt
- Record and collect audio chunks
- Wait for final data and completion
- Review the recording or recover from failure
Conceptual flow for the example above.
Tradeoff to keep in mind
Six states provide a compact starting point, not a universal recorder specification. Pausing, resumable uploads, and persistent drafts may justify additional states once their user-visible behaviour is defined.