Vivek Jalondhara. All writing

React

Microphone Permissions Belong in the Recording State Machine

By Vivek Jalondhara · 5 min read
Published

Treat microphone access as an asynchronous workflow with pending, denied, unavailable, and abandoned outcomes before recording starts.

A recording interface often shows a large Record button and leaves microphone permission to a catch block. That hides an important interval: the person has asked to record, the browser may be waiting for a decision, and no audio is being captured yet.

This educational Ecoscript scenario examines that permission interval and its connection to a React recording controller. The portfolio confirms frontend recording and API integration work. The specific state model and helper below are proposed examples, not claims about the deployed application.

Represent a request that has not finished

Calling getUserMedia initiates an asynchronous request. The user can leave the browser prompt unanswered, so the returned Promise may remain pending. A requesting-access state lets the interface explain what is happening without announcing that recording has begun.

Disable repeated capture attempts while that request is active, and retain a way to leave the workflow. A timer may offer additional guidance, but it should not relabel an unanswered permission prompt as a denied request. Those outcomes require different explanations and different recovery actions.

Do not reduce every failure to refusal

A request can fail because permission is blocked, the required device is missing, the hardware cannot be read, or requested constraints cannot be satisfied. Browser policy and document configuration also matter. In particular, a NotAllowedError does not establish that the person deliberately clicked a deny button.

Map technical failures to specific, cautious messages. “Microphone access is blocked” leaves room for browser or site settings. “No matching microphone was found” suggests checking the device. Keep the diagnostic category available for troubleshooting without exposing unnecessary device details in ordinary logs.

Treat prior permission as a hint

A stored permission decision does not guarantee that a new capture attempt will succeed. The device may have changed, become unavailable, or fail the requested constraints. The state machine should enter recording only after acquiring a usable stream and successfully starting the recorder.

Feature and environment checks belong before the request. Verify that the required media APIs are available in the current context, and present a supported recovery path when they are not. Avoid promises about automatic prompting or device selection that depend on a browser’s individual behaviour.

Handle an abandoned request that later succeeds

The person can navigate away while the prompt remains open and approve it afterward. A stale callback must not attach that stream to a different recording session. The old attempt should recognize that it no longer owns the result and stop every track it just acquired.

The helper below checks ownership and hands the stream to an adoption callback synchronously. Its cancellation is logical: it prevents adoption of a stale stream, rather than claiming to close the browser permission prompt. The caller tracks attempt identity, registers accepted resources during adoption, and owns their later cleanup.

Keep resource ownership separate from screen labels

React state can describe requesting, recording, or failed while a controller owns the actual stream and recorder. A component ref or dedicated controller can retain those browser objects without treating them as renderable data. The owner must release tracks when the relevant session ends.

Effect cleanup can dispose resources when the owning component unmounts. It must also account for work that resolves after cleanup has run, which is why the stale-result check remains necessary. If recording is allowed to continue across navigation, place ownership above the changing page instead of relying on a page-local effect.

Verify the permission interval explicitly

Use a controllable Promise to simulate an unanswered request, a rejection, and a successful stream delivered after cancellation. Verify that only the current attempt adopts the stream and that abandoned tracks are stopped exactly as intended. These checks can exercise ownership without activating a real microphone.

Then test the supported browsers with actual permission settings and device changes. Mocked requests cannot prove the wording or placement of a browser prompt. The key observation is that microphone access changes the product’s state before recording begins, so it deserves the same deliberate recovery design as the recording itself.

Release a stream returned to an abandoned attempt

The caller supplies an ownership check, a capture function, and a synchronous adoption callback that registers the accepted stream. Stale streams are stopped; accepted streams still need session cleanup.

Illustrative example · TypeScript · Simplified for explanation

async function acquireForAttempt(
  isCurrent: () => boolean,
  acquire: () => Promise<MediaStream>,
  adopt: (stream: MediaStream) => void
): Promise<void> {
  let stream: MediaStream;
  try {
    stream = await acquire();
  } catch (error) {
    if (isCurrent()) throw error;
    return;
  }
  if (!isCurrent()) {
    for (const track of stream.getTracks()) track.stop();
    return;
  }
  try {
    adopt(stream);
  } catch (error) {
    for (const track of stream.getTracks()) track.stop();
    throw error;
  }
}
How the example fits together
  1. Create a capture attempt identity
  2. Request microphone access
  3. Adopt only the current attempt’s stream
  4. Release stale or finished resources

Conceptual flow for the example above.

Tradeoff to keep in mind

An ownership guard handles late results but cannot force the browser to dismiss a permission prompt. Browser-specific permission recovery still requires a tested user flow.

Related project: Ecoscript →

References & further reading

Related posts