Tighten the feedback screen layout on mobile viewports so it fits
comfortably on small phone screens and reads naturally without
horizontal scroll or cramped controls.
Wrap the buttons in the idle modal so they stack vertically on
mobile viewports.
The horizontal layout was cramped on small screens; a column layout
gives each button a comfortable touch target and reads more
naturally on narrow displays.
Apply the same approach as the PiP screen to the mobile control
bar: make sure it never overflows on small phone screens by
collapsing some controls into the overflow menu when there is not
enough horizontal room.
livekit-client leaks the raw WebSocket error Event as an unhandled
rejection when the signal WebSocket errors after connect. This is
mostly seen on Firefox and coincides with signal reconnects.
The event carries zero diagnostic content — the close reason is
already logged by the SDK — so it only adds noise to error
tracking.
Filter it out from our reporting.
Upstream issue: https://github.com/livekit/client-sdk-js/issues/2062
Also consider `AbortError` with a message like "Timeout starting
video/audio source" as an "already in use" device error.
This can happen for other reasons in theory, but in practice most
occurrences are caused by another application still holding the
camera or microphone.
Route it through the existing device-in-use handling so users get
the same clear explanation as with the standard error, instead of a
generic failure.
Some versions of Firefox on Windows do not raise the
`NotReadableError` that LiveKit expects when the camera or
microphone fails to start. Instead, they surface an `AbortError`
with a message explaining the browser could not start the video or
microphone input.
This case was observed in PostHog error tracking and was not
handled, so users hit an unhelpful failure state.
Detect that specific `AbortError` and route it through the same
device-in-use / not-readable handling as the standard error, so
users get a clear explanation.
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.
The toast is enqueued on `ParticipantConnected`, which livekit-client
fires before the participant's track publications exist. At that
point `getTrackPublication(Camera)` is `undefined`, and the track
ref used by the toast is frozen that way — the toast never
re-subscribes to the participant.
Downstream effects:
* LiveKit's `useIsMuted` initializes to `false` when there is no
publication, so the tile first paints with
`data-lk-video-muted="false"`, which the stock CSS maps to
placeholder `opacity: 0` — a blank box.
* Its effect then subscribes to `mutedObserver`, which defaults to
`true` for the same situation, triggering a second render and
only then starting the 200 ms fade-in. That render-effect-render
transition is the visible lag behind the text.
* Because the track ref never updates, no `VideoTrack` ever mounts.
When the camera is later subscribed, the placeholder fades out
again, leaving the tile empty.
Re-derive the track ref inside the toast when the participant's
publications become available, so `useIsMuted` sees the real state
from the start and the tile renders the video (or a stable
placeholder) instead of a blank box.
`MuteButton` owned the confirmation dialog state locally. On the
participant tile, the button lives inside `FadeOverlay`, which
conditionally mounts its children based on tile mouse activity
(3 s idle timeout). Once the user moved the pointer onto the
react-aria portal, mouse events on the tile stopped, the idle timer
expired, `FadeOverlay` unmounted `MuteButton`, and the dialog was
destroyed with it — hence the "closes randomly" behavior.
Render the dialog once at the VideoConference level via a shared
`MuteAlertDialogProvider`, so its lifetime is independent from any
button that opens it.
A regression introduced with the custom virtual background feature
(16daf7b8, March 26): every blur init on Firefox, and every update
call, was going through `init_virtual_background`, which raised an
error because there is no virtual background to initialize in that
code path.
The existing guard was incorrect. Replace it with an early return,
so `init_virtual_background` is only executed when there is actually
a virtual background to initialize.
This does not fix the other virtual background issues that currently
prevent some users from activating their camera; those will be
addressed separately.
Send an analytics event whenever starting or stopping a recording
fails, so we can monitor how often it happens.
It also gives support the context needed to understand what went
wrong when inspecting a user's session.
After the recent refactoring, `MoreControls` only renders once. On
that first render, the container ref is `null`, and when it later
gets a reference to the div, the ref update does not trigger a
re-render.
As a result, the additional controls were never showing up.
Switch from a ref to a state to track the container element. State
updates do trigger a re-render, so the controls now show as
expected once the container is available.
`connect()` calls can be aborted by a `disconnect()` before the
join completes — typically when the user leaves, refreshes,
navigates away early, or when the component remounts. This is
expected behavior, not a failure.
Stop reporting these as errors. Track them as analytics events
instead, so we can still monitor their volume without polluting
error dashboards.
Volume is relatively low, but keeping visibility helps troubleshoot
users who complain about difficulty connecting to a room, and could
also flag a component that is re-rendering and killing the
connection.
Bump livekit-client 2.20.0 -> 2.21.0 and
@livekit/components-react 2.9.21 -> 2.9.23. Patch/minor upgrade
with data streams v2, data channel flow control and reliability
fixes, iOS Safari double-playback fix, and a build-time type
resolution fix for `skipLibCheck: false`.
No breaking API changes.
Minor version upgrade with bug fixes and internal improvements. No
breaking changes, API deprecations or required actions on our side.
Highlights:
* Better handling of `sendBeacon` quota rejections so events are no
longer silently dropped.
* Improved error catching for synchronous throws from a
monkey-patched `window.fetch`.
* Source map improvements for easier debugging.
Bump i18next from 26.3.1 to 26.3.6 and react-i18next from 17.0.8 to
17.0.10. Both are patch upgrades with bug fixes and no breaking
changes, requiring no code modifications on our side.
i18next:
* 26.3.6: widen the optional TypeScript peer dependency range to
include v7.
* 26.3.1: fix a regression in type definitions related to
`keyPrefix` to prevent incorrect type pollution.
react-i18next:
* 17.0.10: improve warning messages for `useTranslation` and
`Trans` to help debug common issues, especially in monorepos.
Add a new development-only warning when `useTranslation`
suspends while loading translations.
* 17.0.9: add TypeScript 7 support and fix related type-checking
issues with the `<Trans>` component.
Accept `application/x-www-form-urlencoded` requests on the user
token endpoint, in addition to the existing JSON support.
This aligns the endpoint with the OAuth 2.0 specification for token
endpoint requests (RFC 6749, sections 3.2 and 4.4.2), so standard
OAuth 2.0 client libraries can call it without any customization,
while keeping backward compatibility with existing JSON clients.
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.
The screen-share error modal was tailored to macOS and did not work
correctly on other operating systems.
Make it OS-aware so it also handles Windows properly, showing the
right guidance for each platform.
Also open the OS settings link in a new tab, so the user is not
disconnected from the ongoing meeting when following it.
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.
Filter out harmless `ResizeObserver loop limit exceeded` and
`ResizeObserver loop completed with undelivered notifications`
errors via `beforeSend`.
Why this is safe:
* These are W3C spec-mandated browser guards that defer notification
delivery to the next frame when callbacks alter layout during
render. They do not cause JS runtime exceptions or break the UX.
Why we actually need to filter them:
* Telemetry platforms like PostHog do not stack/group these well,
frequently generating distinct error events per browser engine
and version.
* The unique variants flood reporting dashboards and trigger
false-positive alerts that clutter real issue triage.
* Switch toolbar horizontal alignment from `marginRight` to
`transform: translateX()`, so it no longer triggers layout reflows
during ResizeObserver cycles and stops the "ResizeObserver loop"
error.
* Replace the unstable `shift * 2` margin heuristic with a direct
1:1 positional delta (`offsetX + shift`).
* Decouple CSS transitions: use the individual CSS `translate`
property for the slide-up/down animations, leaving `transform`
free for dynamic horizontal positioning.
Copy the `formatChatMessageLinks` function locally so we can iterate
on it without patching the upstream dependency.
Use the local copy to trim `\n` characters at the beginning and end
of chat messages, which were leaking into the rendered output.
Introduce dual thresholds (1100px wide, 1050px narrow) for switching
the control bar between the expanded inline controls and the
collapsed menu.
The 50px deadband absorbs the width changes caused by rendering
5 buttons vs. 1 button, preventing an infinite layout oscillation
and the resulting `ResizeObserver loop` errors.
The vendored ConnectionObserver collected connection data that never
turned out to be useful for debugging.
Remove it to reduce dead code, and re-add a targeted observer later
if a concrete debugging need shows up.
- Only call `setSinkId` when supported and the device is actually
enumerated: LiveKit can fall back to a stale id on browsers (e.g.
WebKit) that expose no such device, making `setSinkId` throw
`NotFoundError`.
- Await `audio.play()` and reset the playing state on failure, to
avoid a stuck button and an unhandled rejection.
- Use an absolute `/sounds/uprise.mp3` URL so the asset resolves
regardless of the current SPA route.
401 responses were not handled by the user preferences sync, which
could leave the app in an inconsistent state when the session had
expired.
Handle the 401 case explicitly and report the error through the
telemetry module so it stays visible without crashing the flow.
The "unreachable external home URL" check was reporting failures as
errors. In practice, it fired a lot for users behind corporate
networks that cannot reach our public landing page, which is
expected behavior and not something to investigate.
Capture it as a regular telemetry event instead of an error, so it
still gives us visibility on the frequency of the case without
polluting error 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.
Introduce a new handling flow for the case where the operating
system itself is blocking browser access to the microphone or
camera, rather than the browser's own permission.
Detect the situation and surface guidance to the user, so they know
they need to allow the browser to access their microphone/camera in
the OS settings.
Only a minority of users are impacted, but the failure mode is very
confusing when it happens. Hopefully this reduces the amount of
support requests around it.
Fix a minor issue on the join screen: the page title was missing the
meeting id, even though the hook's documentation stated it should be
included.
Align the actual behavior with the documented one so the meeting id
now shows up in the browser tab title.
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.
Forward `console.error` calls to PostHog on top of the existing
exception capture.
This is experimental: the goal is to gather more information about
buggy situations that do not surface as thrown exceptions today.
May be reverted or filtered depending on the signal-to-noise ratio.
Hide the ProConnect button (only used by the Dinum frontend) when
the device viewport is not wide enough to display it cleanly, so it
does not overflow or break the layout on smaller screens.
When a user clicks the microphone or camera toggle while the
corresponding permission is denied, trigger a permission prompt via
`getUserMedia` instead of silently doing nothing.
This gives users a clear path back to granting access without having
to dig into the browser settings themselves.
Add a sound tester next to the selected output device in the speaker
select menu, so users can play a test sound and confirm they picked
the right speaker.
Inspired by the microphone gauge added previously, and requested by
users.
Add an audio level gauge next to the selected microphone in the mic
select menu, so users can see at a glance whether their microphone
is actually picking up sound.
Inspired by Google Meet's mic picker, and requested by users.