How do i work with virtual backgrounds and mirrore...
# ask-ai
w
How do i work with virtual backgrounds and mirrored images? The user should have its own video tile mirrored so its a normal experience. Everyone else should not be mirrored. However, when i add a virtual background image with text, they should never be mirrored. But if i flip the image the user experience the background mirrored, and if i dont, the other participants are experiencing it wrong.
t
Thanks for reaching out, Marcus. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — this is a great question and a common source of confusion. I’ll walk through the cause and a clear approach so the local user sees a mirrored self-view, everyone else sees a correctly oriented video, and virtual backgrounds with readable text never appear reversed. Summary of the issue you described • You want the local user’s preview (their own tile) mirrored for a natural “mirror” experience. • Remote participants should see the non-mirrored (true) video. • Background images containing text must appear readable to everyone (never mirrored for viewers). What’s happening and why • Mirroring is a UI/display transformation applied locally — it does not need to be part of the outgoing video stream. • If you mirror the published/composited stream, remote viewers receive a mirrored image (text reversed). • If you don’t mirror at all, the local user’s preview feels unnatural (they expect a mirror). • Virtual backgrounds that contain text introduce an extra twist: if the background is composited after a mirrored camera feed, text can end up reversed for others unless handled correctly. Recommended approach (what to implement) 1. Mirror only the local preview UI ◦ Enable mirror mode on the local renderer used for the user’s own preview (e.g.,
LocalVideoTrack
preview view). ◦ Do NOT apply mirror mode to the published/composited track or to remote renderers. ◦ Result: local user sees a mirrored self-view, remote viewers see the true orientation. 1. Ensure the outgoing/composited video is non-mirrored ◦ When you composite the camera feed with the virtual background for publishing, publish a non-mirrored final frame. ◦ The published track must be the true (non-flipped) image so remote participants and recording see correct orientation. 1. Handle virtual backgrounds that contain text ◦ Pre-flip (horizontally invert) the background image before compositing if you are compositing a mirrored local preview into the outgoing frame. ◦ Better practice: Composite from the unmirrored camera frame and use an unflipped background image. Then locally mirror only the preview UI. This avoids having to flip backgrounds at all. ◦ If you cannot composite from an unmirrored feed for some reason, pre-flip the background image so when the mirrored camera feed is used it yields a final non-mirrored result for remote viewers. 1. Renderer selection & configuration ◦ Use your platform’s renderer settings to enable mirror only on the local preview (e.g.,
VideoTextureViewRenderer
or
SurfaceViewRenderer
on Android) and ensure remote renderers are not set to mirror. ◦ Confirm the final frame you publish (the track) is not mirrored. Short implementation checklist • [ ] Local preview view: mirror = true • [ ] Remote participant views: mirror = false • [ ] Published/composited track: mirror = false • [ ] Virtual background with text: either composite from an unmirrored camera frame (preferred) or pre-flip the background image before compositing Questions that will help me give exact code or steps • Which platform / SDK are you using (Web, iOS, Android, or another)? • Are you compositing backgrounds client-side before publishing, or are you using a server-side compositor? • If client-side, which renderer class are you using for the local preview (so I can show the exact property to toggle)? If you tell me your platform and whether composition is client- or server-side, I’ll give a short code example showing: (1) how to enable mirror only on the local preview and (2) how to composite/publish a non-mirrored track so text in backgrounds remains readable for everyone. Sources: client-sdk-android | livekit/livekit_composite | DeepWiki
w
right now i mirror the video tile with tailwind "scale-x-[-1]" which in turn mirrors both the user and the background. is there a different way in livekit to only mirror the actual video track?
t
Thanks — that makes sense. You’re right to avoid using Tailwind’s
scale-x-[-1]
because that flips the whole DOM element (video + background + overlays). LiveKit provides renderer-level mirroring so you can mirror only the video track (the local self-view) without affecting virtual backgrounds or UI. What to do • Stop using
scale-x-[-1]
on the video tile. • Use LiveKit’s renderer mirror option for the local preview only, and keep remote renderers and the published/composited track unmirrored. • If you composite virtual backgrounds client-side, composite from the unmirrored camera frame (preferred). If you must composite from a mirrored frame, pre-flip the background image so text stays readable for remote viewers. Quick platform-specific notes — which mirror setting to use • Web (React / @livekit/components-react): use the
mirror
prop on the video renderer / ParticipantTile so only the video is mirrored instead of the whole element. ◦ Example: <videorenderer ... mirror={true}/> or ParticipantTile with
mirror
set. • iOS: set
videoRenderer.mirror = true
on your
VideoTextureViewRenderer
/
VideoView
. • Android: set
videoRenderer.mirror = true
on
VideoTextureViewRenderer
or
SurfaceViewRenderer
. • Flutter: set
mirrorMode: true
on the
VideoRenderer
widget. Why this fixes your problem • Renderer-level mirroring flips only the video buffer. Overlays and background images remain their original orientation, so text in virtual backgrounds isn’t reversed for remote viewers. • The published/composited track remains non-mirrored so everyone else sees the correct orientation. One question to give you exact code • Which platform/SDK are you using (Web React, plain Web, iOS, Android, or Flutter)? • Are you compositing virtual backgrounds client-side before publishing, or using a server-side compositor? Tell me those and I’ll post a short code snippet showing exactly where to toggle mirror mode and how to ensure your background text remains readable. Sources: React Core and Components | client-sdk-android | livekit/livekit_composite | DeepWiki
w
so this is what i use in the videocall "<VideoTrack key={track?.publication?.trackSid || uuidv4()} className={videoClass} trackRef={track} />" how do i mirror that track/component without tailwind styling?
t
Thanks — I see you’re rendering the LiveKit
VideoTrack
and currently using Tailwind’s
scale-x-[-1]
, which flips the entire DOM (video + background). To mirror just the video track (local preview) without affecting overlays or virtual backgrounds, use the built-in
mirror
prop on the LiveKit
VideoTrack
component. If this is React with @livekit/components-react, update your JSX like this:
Copy code
<VideoTrack
  key={track?.publication?.trackSid || uuidv4()}
  className={videoClass}
  trackRef={track}
  mirror={track?.publication?.isLocal} // mirror only local track
/>
Notes: • Use
mirror={true}
only for the local participant’s track (example above uses
isLocal
). • This applies mirroring at the renderer level so overlays and background elements are not flipped. • The published/composited track remains unmirrored, so remote participants see the correct orientation. If you want, tell me: • whether you’re using
@livekit/components-react
specifically, and • how you’re rendering virtual backgrounds (separate overlay element or composited into the outgoing frame), and I’ll give a tiny tailored snippet showing the local preview + unflipped background setup. Sources: Bringing AI avatars to voice agents | Bringing AI avatars to voice agents | LiveKit coming to React Native! | LiveKit coming to React Native!
w
i am using this: "(alias) const VideoTrack: (props: VideoTrackProps & React.RefAttributes<HTMLVideoElement>) => React.ReactNode import VideoTrack The
VideoTrack
component is responsible for rendering participant video tracks like
camera
and
screen_share
. This component must have access to the participant's context, or alternatively pass it a
Participant
as a property. @example <VideoTrack trackRef={trackRef} /> @see — @alert-easter-19258/components-react#ParticipantTile | ParticipantTile _@public_" but with you solution i get this error: Type '{ key: string; className: string; trackRef: TrackReference; mirror: boolean; }' is not assignable to type 'IntrinsicAttributes & VideoTrackProps & RefAttributes<HTMLVideoElement>'. Property 'mirror' does not exist on type 'IntrinsicAttributes & VideoTrackProps & RefAttributes<HTMLVideoElement>'.ts(2322)
t
Thanks — that error is exactly the clue we need. You’re using the low-level
VideoTrack
component, and
VideoTrack
does not accept a
mirror
prop, so TypeScript is correctly preventing that prop from being passed. What I recommend • Use
ParticipantTile
(higher-level component) for local self-view mirroring — it supports the
mirror
prop and applies mirroring at the renderer level so overlays/virtual backgrounds are not flipped. • Keep
VideoTrack
for raw track rendering when you don’t need mirroring; if you must use
VideoTrack
, mirror only the video element (not the whole container) with a targeted style as a fallback. Concrete options 1) Preferred — switch to ParticipantTile (recommended) • Replace your
VideoTrack
usage for the local participant with
ParticipantTile
and set
mirror={true}
only for the local participant. • Example:
Copy code
import { ParticipantTile } from "@livekit/components-react";

<ParticipantTile
  participant={localParticipant}      // or pass participant via context
  mirror={true}                       // mirrors only the video renderer
  className={videoClass}
/>
• This mirrors the video buffer for the local preview and leaves overlays/virtual backgrounds unflipped. The published/composited track remains non-mirrored for remote viewers. 2) If you must keep VideoTrack (fallback) • There is no built-in
mirror
prop on
VideoTrack
. If you cannot switch to
ParticipantTile
, apply mirroring only to the video element itself (not the container) so overlays remain intact: ◦ Add a CSS class that targets the actual <video> element inside the VideoTrack component and apply
transform: scaleX(-1)
there. ◦ Example CSS (scoped):
Copy code
/* target the video element rendered by VideoTrack */
    .my-video .lk-video-element {
      transform: scaleX(-1);
    }
• You’ll need to inspect the DOM structure (or the class name LiveKit places on the video element) to target only the video element. This is less ideal but workable. Quick questions so I can give a drop-in snippet • Are you able to render the local participant via
ParticipantTile
instead of
VideoTrack
? (Yes / No) • Which package version are you using for
@livekit/components-react
? (I can tailor code if you share the version) • How are you rendering your virtual background— as a DOM overlay on top of the video, or are you compositing background into the outgoing track? Reply with those answers and I’ll give the exact JSX/CSS you can paste directly into your code. Sources: client-sdk-js | livekit/livekit_composite | DeepWiki | LiveKit coming to React Native! | Streaming video from a canvas with WebRTC and React | React Core and Components | React Core and Components
w
show me how to use this
Copy code
.my-video .lk-video-element {
      transform: scaleX(-1);
    }
with my VideoTrack
t
Thanks — I’ll show exactly how to mirror only the underlying <video> element inside your
VideoTrack
so overlays and backgrounds aren’t flipped. What we’re doing • Add a parent className to the
VideoTrack
container. • Target the video element LiveKit renders (it uses the class
lk-video-element
) and apply
transform: scaleX(-1)
to that element only. 1) JSX: give the VideoTrack a parent class
Copy code
import { VideoTrack } from "@livekit/components-react";

function ParticipantView({ track }) {
  return (
    <VideoTrack
      trackRef={track}
      className="my-video"    // parent container class
      key={track?.publication?.trackSid}
    />
  );
}
2) CSS: mirror only the video element
Copy code
/* scope to your container so only this video is mirrored */
.my-video .lk-video-element {
  transform: scaleX(-1);
  /* keep transform origin and smoothing if needed */
  transform-origin: center;
  backface-visibility: hidden;
}
Notes and tips • This mirrors only the actual <video> element that LiveKit injects (
.lk-video-element
), not the container or overlays layered around it. • Ensure your CSS file is imported (or include the rule in your global stylesheet). If you use CSS modules, adapt the selector to match the generated class names. • Use this only for the local participant’s tile. For remote participants, omit the
.my-video
class (or conditionally apply it). • If you later switch to
ParticipantTile
, it supports a
mirror
prop natively and avoids manual CSS. If you want, tell me: • how you identify local vs remote tracks in your code, and I’ll show a conditional example that applies the
my-video
class only for the local participant. Sources: React Core and Components | React Core and Components | React Core and Components
w
can you do the css styling in-line instead of a different file?
t
Yes — you can mirror only the underlying <video> element inline, so overlays and virtual backgrounds aren’t affected. Below are two safe options depending on how you render
VideoTrack
in React. Option A — Use a ref and set the video element’s style when it mounts • This targets the actual HTMLVideoElement rendered by LiveKit and flips it inline.
Copy code
import React, { useRef, useEffect } from "react";
import { VideoTrack } from "@livekit/components-react";

function LocalVideo({ track }) {
  const videoRef = useRef<HTMLVideoElement | null>(null);

  useEffect(() => {
    const el = videoRef.current;
    if (!el) return;
    // apply inline mirroring to only the video element
    el.style.transform = "scaleX(-1)";
    el.style.transformOrigin = "center";
    el.style.backfaceVisibility = "hidden";

    // cleanup if the element is removed/replaced
    return () => {
      if (el) el.style.transform = "";
    };
  }, [track]);

  return (
    <VideoTrack
      trackRef={track}
      key={track?.publication?.trackSid}
      // pass the ref to the actual DOM video element via `ref`
      ref={videoRef as any}
    />
  );
}
Option B — Use the component’s render callback (if available) to style the element • Some LiveKit components let you access the media element via a callback prop; if your
VideoTrack
exposes the actual element through an
onRenderElement
(or similar) prop, use that to set inline styles.
Copy code
function LocalVideo({ track }) {
  const onRenderElement = (el: Element | null) => {
    if (el instanceof HTMLVideoElement) {
      el.style.transform = "scaleX(-1)";
      el.style.transformOrigin = "center";
      el.style.backfaceVisibility = "hidden";
    }
  };

  return (
    <VideoTrack
      trackRef={track}
      key={track?.publication?.trackSid}
      // hypothetical prop name; if your version supports it, use it
      onRenderElement={onRenderElement as any}
    />
  );
}
Important notes • Only apply this for the local participant’s tile. Remote tiles should not be flipped. • Inline styling like this mirrors only the video element (not the container), preserving overlays/virtual-backgrounds layered outside the video element. • If your
VideoTrack
does not accept
ref
or an element callback, you can wrap it in a container, query for
.lk-video-element
inside that container in useEffect, and set the inline style on that found element. If you want, tell me: • which exact version of
@livekit/components-react
you’re using, and • whether
VideoTrack
accepts
ref
or an element callback in your build, and I’ll give the exact drop-in code for your version (or the DOM-query fallback). Sources: Managing Video Quality During Network Congestion | LiveKit | Client SDKs | livekit/livekit_composite | DeepWiki