Align LowerAllHandsButton with the MuteEveryoneButton pattern by
wrapping it with the AdminOrOwnerOnly component.
This prevents mounting its hooks and rendering its logic for users
who are not admin or owner, instead of relying on an internal check
that still ran on every re-render.
Extract the participant count out of the participant badge into a
dedicated, optimized component.
This isolates the subscription to the participant count so it no
longer triggers a re-render of the whole toggle when the count
changes.
Reorganize all participant list related code elements into a
dedicated feature folder, so related components, hooks and helpers
are grouped together and easier to locate.
- Dissociate the chat observer (which persists messages in a store)
from chat rendering.
- Do not keep the chat mounted in the DOM anymore.
- Recompute the minimal amount of data when a participant renames.
- Memoize most of the layout.
- Drop the manual textarea row-size computation: recent React Aria
already handles it.
Known regressions still to solve: focus behavior on the input,
scroll behavior on message updates, and edge cases around list
sizing.
Not sure yet whether there is a better way to memoize the
virtualized list; open to feedback.
Memoize the ActiveSpeaker component used in the push-to-talk
component so it does not re-render on unrelated parent updates when
its props have not changed.
Reorganize all chat-related code elements into a dedicated feature
folder, so chat components, hooks and helpers are grouped together
and easier to locate.
Subscribing to the whole local participant caused re-renders on
every local participant update, even when the consumer only cared
about a specific attribute.
Narrow the subscription so only the relevant attributes trigger a
re-render.
Move the video resolution subscription down into a lower component
so it no longer re-renders the whole Videoconference component on
every resolution change.
The overall approach still needs validation, but this already avoids
the top-level re-render and is a clear improvement over the current
behavior.i
canPublishTrack was mistakenly subscribing to the whole local
participant changes, so every component using this hook re-rendered
on any local participant update, not only on permission changes.
Fix it to subscribe only to the permission attributes.
Restrict the full-screen warning to the local participant tile.
Previously, mounting it on every participant tile meant that when
the local participant toggled their camera or microphone, the
warning re-evaluated and triggered a re-render on every tile.
Push state down into the participant metadata subtree so a raised
hand change no longer re-renders the whole metadata block.
Use the children-as-props pattern so only the item actually related
to the raised hand re-renders when its state changes.
This also better encapsulates the color-update logic tied to the
raised-hand state.
useRaisedHandPosition is called on every participant tile, so any
unnecessary subscription in it multiplies re-renders across the
whole conference.
Reduce the number of events subscribed to for remote participants
to the strict minimum, while keeping the subscription to the
relevant attributes of the local participant intact.
Encapsulate the participant metadata rendering in a clear boundary
component, especially by pushing the raised-hand logic down into a
dedicated child.
This way, when a participant raises their hand, only the child
component re-renders instead of the whole ParticipantTile.
Reduce the set of events the chat subscribes to, keeping only those
that actually affect its rendering.
This avoids frequent re-renders triggered by unrelated events on
the room or participants.
In the participant tile, there is no need to read the pinned track
through a Valtio snapshot, which would trigger a re-render every
time the pinned track changes, for every participant tile.
Switch to an imperative read of the store to avoid these unnecessary
re-renders.
Reorganize all components related to the participant tile into a
dedicated feature folder, so the code organization is clearer and
related components are grouped together.
The useSettingsDialogs hook only encapsulated Valtio store
manipulation that should be declared at the module level. Keeping it
as a hook triggered unnecessary re-renders in components that used
it, subscribing to the store.
Remove the hook and use the store actions directly at the module
level.
Move the keyboard shortcut registration down into the
SettingsDialogProvider leaf component, so shortcut-related
re-renders no longer bubble up and re-render the whole
Videoconference component.
Reduce the set of events the ParticipantToggle subscribes to, so it
does not re-render on every LocalParticipant event but only on the
ones that actually affect its rendering.
Refactor the connection observer, previously a hook consumed inside
the Videoconference component, into a dedicated leaf component in
the tree.
This avoids re-rendering the whole Videoconference component every
time the connection state changes; only the leaf reacts to it.
Reduce the set of events that trigger an update of the participant
list in the chat, keeping only those relevant to display: attribute
and name changes.
This avoids unnecessary re-renders when other, unrelated participant
events fire.
At this stage, the chat is kept mounted in the DOM at all times to
preserve state and keep receiving new messages. As a consequence,
every chat message ends up rendered in the DOM, which can bloat it
significantly in large meetings with hundreds of participants
exchanging messages.
Virtualize the chat message list so only the few messages actually
visible are rendered in the DOM.
The fact that the chat is always mounted in the DOM will be
addressed in a later commit.
The InviteDialog open state was managed very high in the React tree,
which triggered a re-render of the whole conference tree whenever
the dialog was opened or closed.
Push the state down to a narrower component to limit the scope of
re-renders.
Part of a broader effort to isolate as much as possible what should
re-render, so we can then focus on tackling the real performance
issues.
Extract the code responsible for announcing pinned track changes
(for accessibility) into a well-scoped leaf component.
Previously, calling the useScreenReaderAnnounce hook inside
StageLayout triggered a re-render of the whole layout subtree.
Encapsulating the announcement logic in a narrow component keeps
those re-renders local to that leaf.
Vendor the pinned track context and its associated hook from
LiveKit, and switch the underlying state management to a Valtio
store.
The LiveKit ContextProvider was injected high in the tree, so any
pinned track change triggered a re-render of a large portion of the
app. With a Valtio store, only the parts of the app that actually
subscribe to the pinned track or need to pin one re-render when
present in the DOM.
Move track manipulation into a leaf component to avoid re-rendering
the whole videoconference tree when a track update occurs.
The new StageLayout component is now responsible for rendering the
video tracks in the relevant layout, while the Videoconference
component keeps the responsibility of building the overall
conference view.
We save re-rendering the ControlBar for every track/layout changes
for example.
Copy the MediaPipe WASM modules into the frontend build output and
serve them locally, instead of loading them from the Google CDN at
runtime.
Mediapipe was a transitive dependency, add it explicitly.
Requested by some members of the community.
It closes#1168
Add vite-plugin-static-copy as a dependency. It will be used to copy
and serve the WASM modules shipped by the MediaPipe JS dependency
from the frontend build output.
Inline the model weights instead of fetching them from a Google
remote location at runtime. The weights do not update frequently, so
inlining removes an external dependency.
Update the calls to the background processors imported from
livekit-track-processor so they use the new instantiation method
instead of the previous, now-deprecated one.
The info panel was crashing when opening a room that was not
registered in the database (with ALLOW_UNREGISTERED_ROOMS=true),
because the API response did not include the room slug.
Instead of adding frontend fallbacks, update the unregistered-room
response to include the slug, keeping the API contract consistent
with registered rooms.
Note: this still relies on the unregistered-room response staying
aligned with the registered-room schema. Any future divergence
between the two responses could introduce similar issues.
A refactoring on the backend side is needed.
It closes#1441.
Add a new setting that controls whether an authenticated user can
rename or modify their display name, or if they must use the one
returned by the SSO.
When the setting is disabled, only anonymous users can set a display
name freely; authenticated users always use their SSO display name.
Requested by many self-hosters.