Skip to main content

Video Overlays

SignalWire renders the video of your room in the cloud. This means that everyone sees the same content, and you could have a virtually unlimited number of connected users: the network link between your computer and SignalWire's server will only need to carry a single video stream, no matter what.

To give your clients metadata about the position of the different video members within the cloud-rendered video stream, SignalWire APIs support the concept of layers. In a few words layers indicate, at any instant of time, the semantic content of each portion of the video stream.

We can use this information for example to show the name of a member whenever the user hovers its mouse over it, and much more. In this demo app, we will just show how to display a selection indicator as follows:

An animated gif showing a SignalWire video room with three participants. The mouse hovers over each participant. This hover action initiates a blur effect on the selected participant video.

Frontend

We will make all our changes to the <Video> component, so let's open Video.js, where we will add the following variables:

// Keep track of the information about the current layout
let currLayout = useRef(null);

// Style and position of the overlay
let [overlayStyle, setOverlayStyle] = useState({ display: "none" });

The first, currLayout, is used to keep track of the object associated to the current layout of the room. We do this because it is the layout object that contains information about the layers.

The second, overlayStyle, is used to control the style of the overlay element in the DOM (in our case, the blue highlighting). We will create the DOM element within the "stream" div that we already have:

<div
id="stream"
style={{
width,
minHeight: 0.5 * screen.height,
position: "relative",
}}
>
<div style={overlayStyle}></div>
</div>

Make sure to set "position: relative" to the parent style: we will need it later when we will define the content of overlayStyle, because we want to position the overlay by using absolute coordinates which have to be relative to the parent "stream" element.

Before that, we need to actually keep track of the layout object. Whenever the APIs send us one in a layout.changed event, we store the layout object with setCurrLayout:

room.on("layout.changed", async (e) => {
currLayout.current = e.layout; // add this line
onRoomUpdate({ layout: e.layout.name });
});

Next, we need to define a function that will take the current position of the mouse pointer and will update the coordinates and style of our overlay. We call it updateOverlay:

function updateOverlay(e) {
if (!currLayout) return;

// Mouse coordinates relative to the video element, in percentage (0 to 100)
const rect = document.getElementById("stream").getBoundingClientRect();
const x = (100 * (e.clientX - rect.left)) / rect.width;
const y = (100 * (e.clientY - rect.top)) / rect.height;

const layer = currLayout.current.layers.find(
(lyr) => lyr.x < x && x < lyr.x + lyr.width && lyr.y < y && y < lyr.y + lyr.height
);
if (layer && layer.reservation !== "fullscreen") {
setOverlayStyle({
display: "block",
position: "absolute",
overflow: "hidden",
top: layer.y + "%",
left: layer.x + "%",
width: layer.width + "%",
height: layer.height + "%",
zIndex: 1,
background: "#0d6efd38",
backdropFilter: "blur(10px)",
pointerEvents: "none",
});
} else {
setOverlayStyle({ display: "none" });
}
}

The function includes some basic logic to check which of the layers is the one under the cursor, then applies its coordinates to the overlay. The parameter e is the parameter of a mouse event, which contains the coordinates of the pointer. If there are no layers under the cursor, we hide the overlay.

Finally, we need to trigger updateOverlay. We do that during mouse events on the "stream" div, which should be updated like this:

<div
id="stream"
style={{
width,
minHeight: 0.5 * screen.height,
position: "relative",
}}
onMouseOver={updateOverlay}
onMouseMove={updateOverlay}
onMouseLeave={updateOverlay}
>
<div style={overlayStyle}></div>
</div>

You can obtain more information about what is currently displayed within a given layer (for example, the memberId) by exploring the fields of the objects in the currLayout.layers array.

You can find the complete application on CodeSandbox, and on GitHub in the branch "extras":