On Chrome and Windows 10, when the camera or microphone is already
in use by another application, the browser rejects
`getUserMedia`. Without dedicated handling, users just saw their
camera or microphone not turning on, without any explanation.
Detect this specific failure and surface a clear message to the
user, so they understand another application is holding the device
and know what to do about it.
Only report `Other` `MediaDeviceFailure` cases as they genuinely
need investigation.
Make sure we do not report the same situation both as a media event
and as a media exception when it is already handled.
Add a small helper that classifies a `getDisplayMedia` failure as a
user, browser, or OS permission denial, or returns null when it is
a genuine error.
Chromium reports denials with explicit, non-localized messages:
* "Permission denied by user" when the user cancels or dismisses
the source picker.
* "Permission denied by system" when the OS blocks capture (e.g.
the macOS Screen Recording privacy setting).
* Plain "Permission denied" for browser-level blocks (site
settings, enterprise policy, permissions-policy).
Firefox and Safari use generic `NotAllowedError` messages, which
fall into the "browser" bucket.
Firefox additionally does not map macOS Screen Recording (TCC)
blocks to `NotAllowedError`: the OS silently returns no capturable
sources, so `getDisplayMedia` rejects with `NotFoundError` ("The
object can not be found here."). Same quirk as the mic/cam OS blocks
handled in `useWatchMediaDeviceErrors` via `isLikelySystemNotFound`.
Behavior on a denied screen-share permission:
* Denials are expected outcomes (picker cancelled by the user, OS
privacy settings, enterprise policy…) and no longer surface as
exceptions in error tracking; capture an analytics event instead.
* Only OS-level blocks get the modal, since it explains how to
unblock them.
Since `ToggleDevice` renders on both the join screen and in the
room, `requestDevicePermission` was reporting in-room denials
through the join-preview handler, inflating the `join_preview_failure`
funnel.
Rename `onJoinPreviewError` to `onMediaPermissionError` and thread
a `path` parameter through, derived from `ToggleDevice`'s existing
`context` prop. In-room failures are now reported under a new
`room_media_failure` code, keeping `join_preview_failure` intact
for existing dashboards.
Introduce a watcher that listens to the microphone stream and detects
when it stays silent, which is often a sign of an underlying issue:
missing OS permissions, a faulty device, or a hardware lock (e.g. a
physical mute switch).
Wire the watcher on both the join and room screens, so users get a
signal that something is off before it turns into an actual meeting
problem.
Also snapshot the state of media devices when the user successfully
joins a meeting, not only when something goes wrong. This gives us
the baseline needed to compute meaningful ratios — for example, the
share of users who join a meeting without granting permissions, or
without a microphone or camera available.
Without a happy-path measurement, the current error-only data has no
denominator to compare against.
Switch calls to `reportError` over to `captureMediaEvent` when the
underlying situation is not an engineering issue to investigate but
rather a media-related event worth tracking (e.g. no camera or
microphone available on the user's device).
`reportError` stays reserved for actual errors that warrant an
engineer's attention.
Handle the "requested device not found" error surfaced in production
when users arrive without a microphone or camera available on their
computer. Some devices also have a hardware button that physically
locks the microphone and makes it invisible to the browser.
Instead of failing loudly, surface a clearer state to the user so
they can still proceed with whatever device is actually available.
Attach a media diagnostics snapshot to the room event handler for
media exceptions. The snapshot captures the state of the user's
setup at the moment of the error (available devices, permission
state, active tracks, etc.), so support has enough context to
troubleshoot user issues without asking them to reproduce.
Move the remaining direct `posthog.capture` calls behind the
telemetry module, so PostHog is only referenced from a single place.
Call sites now use the telemetry API instead of touching PostHog
directly, making it easier to swap the backend later without
changing every call site.
Introduce a telemetry module that exposes a `reportError` helper.
Under the hood it forwards errors to PostHog, but the module is the
only place that knows about PostHog.
Replace `console.error` calls used for error reporting with
`reportError`, so the codebase now goes through a single, consistent
API for telemetry.
This normalizes how errors are reported and makes it straightforward
to swap PostHog for another backend later on, without touching every
call site.