The Video Display Calculator lets you take over how participant videos are arranged. Instead of using our layout, you supply a function that receives the container size and the participants, and returns an absolute position for every video.
It is part of the JavaScript API, so everything on that page about getting the handler applies here too.
| Term | Meaning |
|---|---|
| Video | The video stream of one participant |
| Container | The area of the meeting room in which all videos are displayed |
If you are using an AI coding assistant, copy the text below into it and add a sentence describing what you want to build. AI assistants accessing this page receive a separate, token-optimized version of our full developer documentation, specifically tailored by our engineers for AI coding assistants.
Before writing any code, read this page:
https://www.veeting.com/en/developer-documentation/video-display-calculator
It documents how to take over video layout in a Veeting Rooms meeting
room. Keep these five rules in mind:
1. videoPositions and hiddenContainers are matched by index. A
position whose hiddenContainers entry is still true is NOT shown.
Set it to false for every container you fill.
2. There are 12 containers. Do not return more.
3. The self view is in no participant array. If hasSelfView is true
and I want it shown, add a position for participantId "myself".
4. The function must return synchronously. No promises, no await.
5. If it throws, the room silently falls back to its own layout, so
add error handling I can actually see.
What I want to build:The presentation layout splits the container into two areas. The classic layout has no secondary area, and the primary one always fills the container.
By default, every video sits in the primary area, which covers the whole container. A moderator can move videos into the secondary area; as soon as one video is there, the container is split.

The secondary area can sit against any edge, and it comes in two sizes:
| Value | Meaning |
|---|---|
secondaryPosition | none, left, top, right, or bottom |
secondarySize | 0.15 for the small area, 0.5 for the large one, as a fraction of the container |
You receive both values. Honoring them is up to you, and so is the split: you get raw pixel dimensions for the whole container, not for each area.
Your function is called whenever positions need to be recalculated: on join, on leave, on resize, and when the moderator rearranges the room. It must return synchronously.
The meeting room has 12 video containers. You decide which are used and which stay hidden.
The parameters are positional, so their names are yours to choose. They arrive in this order:
| Order | Parameter | Type | Meaning |
|---|---|---|---|
| 1 | containerWidth | number | Width of the video container, in pixels |
| 2 | containerHeight | number | Height of the video container, in pixels |
| 3 | hasSecondaryArea | boolean | Whether the secondary area is visible |
| 4 | secondaryPosition | SecondaryVideoPosition | Which edge the secondary area sits against |
| 5 | secondarySize | SecondaryVideoSize | 0.15 or 0.5 |
| 6 | primaryParticipants | string[] | Participant IDs in the primary area |
| 7 | secondaryParticipants | string[] | Participant IDs in the secondary area |
| 8 | participantsOrder | string[] | All participant IDs, in display order |
| 9 | participantsMediaInformation | IParticipantsMediaInformation | Media state and video dimensions per participant |
| 10 | hasSelfView | boolean | Whether a self view is present |
| 11 | isSelfviewInSecondary | boolean | Whether the self view belongs in the secondary area |
| 12 | isTVMode | boolean | Whether the room is in TV mode |
The self view is not in any participant array. If
hasSelfViewistrueand you want to show it, add a position for the participant IDmyself. Nothing else adds it for you.
| Property | Type | Meaning |
|---|---|---|
| videoPositions | IVideoPosition[] | One entry per video you want displayed |
| hiddenContainers | boolean[] | 12 entries. false shows the container at that index; true hides it. |
The two arrays are matched by index: the first entry of videoPositions applies to container 0, and so on. A position with hiddenContainers[i] left at true will not appear, which is the single most common mistake here.
An IVideoPosition is:
| Property | Type | Meaning |
|---|---|---|
| participantId | string | Whose video goes in this container, or myself for the self view |
| width | number | Width in pixels |
| height | number | Height in pixels |
| top | number | Pixels from the top of the container |
| left | number | Pixels from the left of the container |
| zIndex | number | Stacking order, if you overlap videos |
| cssClasses | string[] | Optional. Every class must start with video-container-. |
If your calculator throws or returns an empty object, the meeting room silently falls back to its built-in layout. Nobody sees an error, so if your layout does not appear, check the browser console first.
This places the first participant in the center, the others in a row above, and the self view below.

It uses only the primary area.
const videoDisplayCalculator = {
calculatePositions: (containerWidth,
containerHeight,
hasSecondaryArea,
secondaryPosition,
secondarySize,
primaryParticipants,
secondaryParticipants,
participantsOrder,
participantsMediaInformation,
hasSelfView,
isSelfviewInSecondary,
isTVMode) => {
const result = {
videoPositions: [],
hiddenContainers: Array(12).fill(true)
};
// We want to display the videos with a 4:3 format
const threeToFour = 3 / 4;
// The main video is placed in the center, 65% of the container size
const mainContainerSizePercentage = 0.65;
let mainContainerWidth = 0;
let mainContainerHeight = 0;
let mainContainerTop = 0;
let mainContainerLeft = 0;
if (Array.isArray(participantsOrder) && participantsOrder.length > 0) {
mainContainerWidth = containerWidth * mainContainerSizePercentage;
mainContainerHeight = mainContainerWidth * threeToFour;
if (mainContainerHeight > containerHeight * mainContainerSizePercentage) {
mainContainerHeight = containerHeight * mainContainerSizePercentage;
mainContainerWidth = mainContainerHeight / threeToFour;
}
mainContainerTop = (containerHeight - mainContainerHeight) / 2;
mainContainerLeft = (containerWidth - mainContainerWidth) / 2;
// Adding the main video
result.videoPositions.push({
participantId: participantsOrder[0],
width: mainContainerWidth,
height: mainContainerHeight,
top: mainContainerTop,
left: mainContainerLeft,
zIndex: 1
});
result.hiddenContainers[0] = false;
const numberOfOtherVideos = participantsOrder.length - 1;
if (numberOfOtherVideos > 0) {
// Adding all other videos in the top row
let otherContainersWidth = containerWidth / numberOfOtherVideos;
let otherContainersHeight = otherContainersWidth * threeToFour;
if (otherContainersHeight > mainContainerTop) {
otherContainersHeight = mainContainerTop;
otherContainersWidth = otherContainersHeight / threeToFour;
}
const otherContainersTop = (mainContainerTop - otherContainersHeight) / 2;
let otherContainersLeft = (containerWidth / 2)
- ((otherContainersWidth * numberOfOtherVideos) / 2);
for (let i = 1; i < participantsOrder.length; i++) {
result.videoPositions.push({
participantId: participantsOrder[i],
width: otherContainersWidth,
height: otherContainersHeight,
top: otherContainersTop,
left: otherContainersLeft,
zIndex: 1
});
result.hiddenContainers[i] = false;
otherContainersLeft += otherContainersWidth;
}
}
}
if (hasSelfView) {
// The self view is never in participantsOrder, so add it explicitly.
const selfViewHeight = containerHeight - mainContainerTop - mainContainerHeight;
const selfViewWidth = selfViewHeight / threeToFour;
result.videoPositions.push({
participantId: "myself",
width: selfViewWidth,
height: selfViewHeight,
top: containerHeight - selfViewHeight,
left: (containerWidth / 2) - (selfViewWidth / 2),
zIndex: 1
});
// Reveal the container this position was pushed into. Without this the
// self view is calculated correctly and then never shown.
result.hiddenContainers[result.videoPositions.length - 1] = false;
}
return result;
}
};
window["wlvmrApiReady"] = (handler) => {
if (!window[handler]) {
console.error(`Handler window.${handler} not found!`);
return;
}
window[handler].setVideoDisplayCalculator(videoDisplayCalculator);
};type SecondaryVideoPosition = "none" | "left" | "top" | "right" | "bottom";
enum SecondaryVideoSize {
small = 0.15,
large = 0.5
}
interface IVideoPosition {
participantId: string;
width: number;
height: number;
top: number;
left: number;
zIndex: number;
cssClasses?: string[];
}
interface IVideoDisplayConfig {
videoPositions: IVideoPosition[];
hiddenContainers: boolean[];
}
interface IMediaInformation {
audioMuted?: boolean;
videoMuted?: boolean;
hasAudio?: boolean;
hadAudio?: boolean;
hasVideo?: boolean;
// Bad connectivity sometimes stops the video: hasVideo is false and
// hadVideo is true, which means we expect it to come back.
hadVideo?: boolean;
videoWidth?: number;
videoHeight?: number;
}
interface IParticipantsMediaInformation {
[participantId: string]: IMediaInformation;
}hiddenContainers entry at true, so the video never appears.myself.video-container-.secondarySize is always 0.15. It is 0.5 for the large secondary area.Contact our team to discuss the details.