The Video Display Calculator API is part of the Javascript APIs. Front-end Javascript developers can override the default video positioning by implementing their own layout and position the participant videos freely in the video container.
Videos are displayed depending on the container size of the area where the meeting room places the videos. The position of each participant video is defined in absolute terms (x, y, width and height) relative to the main container.
Check out the Javascript APIs and iFrame embedding documentation pages to learn more about these topics.
Term | Definition |
---|---|
Video, Participant Video | The video stream of a participant. |
Container, Video Container | The area of the meeting room in which all videos are displayed |
The presentation room layout has two distinct areas within the video container: the primary and secondary area.
Note: There is no secondary area in the classic meeting room layout, the primary area always covers the entire container.
By default, all videos are displayed in the primary area. In this case, the primary are covers 100% of the video container. The moderator can move videos between the primary and the secondary area. As soon as at least one video is displayed in the secondary are the container is split into two parts: the primary area which consist of 85% of the container and the secondary area which uses the remaining 15% of the container.
The secondary area can be moved around all corners (top, right, bottom, left)
The video display calculator is triggered whenever the video positions should be updated. You have to calculate the absolute position and size of each video within the container and return an array of Video Position objects.
The system can display up to 12 video containers. Base on your own rules and the number of participants, you will choose to show or hide them.
The video display calculator receives the following paramters:
Parameter name | Data type | Explanation |
---|---|---|
containerWidth | number | The width of the video container, in pixels |
containerHeight | number | The height of the video container, in pixels |
hasSecondaryArea | boolean | True if secondary display is visible, false otherwise. |
secondaryPosition | SecondaryVideoPosition | The region where the secondary video display area is positioned |
secondarySize | SecondaryVideoSize | The size of the video container, in per percentage of the width or height of the entire container |
primaryParticipants | string[] | The list of participant IDs attached to the primary area |
secondaryParticipants | string[] | The list of participant IDs attached to the secondary area |
participantsOrder | string[] | The ordered list of participant IDs |
particpantsMediaInformation | IParticipantsMediaInformation | An object containing IMediaInformation for each of the participants in participantsOrder . IMediaInformation contains information about the size and state of the participants video. |
hasSelfView | boolean | True if there is a self-view, false otherwise |
isSelfviewInSecondary | boolean | True if the self view is displayed in the secondary container, false otherwise |
isTVMode | boolean | True if the meeting room is displayed in the TV mode, false otherwise |
Important Note: The self view participant ID is NOT part of any participant ID array. If there is a self view (
hasSelfView === true
) you may decide to add the self view. Use the stringmyself
to add the selfview to the output data structure.
The output structure contains an array of video positions whereas the position elements will be applied in that order to the available video containers. The output structure also consist of an array of booleans, indicating if an element should be hidden or not.
Parameter name | Data type | Explanation |
---|---|---|
videoPositions | IVideoPosition[] | An array of IVideoPosition objects for all visible video containers |
hiddenContainers | boolean[] | An array of size 12 with false if the video should be displayed and true otherwise |
The video position interface is defined as follows:
Parameter name | Data type | Explanation |
---|---|---|
participantId | string | The participant ID of the video which should be displayed in this container |
width | number | The width of the video container, in pixels |
height | number | The height of the video container, in pixels |
top | number | The number of pixels from the top of the container |
left | number | The number of pixels from the left of the container |
zIndex | number | The z-Index of the element, if you want to position video displays on top of each other |
cssClasses | string[] | CSS classes which will be applied to the video container. Important: The CSS classes MUST be prefixed with 'video-container-' |
If the video display calculator throws an error or returns an empty object the system will revert to the internal position calculator instead.
The following example code places the first participant in the center of the screen, all other participants in one row above the first participant and the self view below the participant:
This example does not use the secondary area, all videos are displayed in the primary area.
const videoDisplayCalculator = {
calculatePositions: (containerWidth,
containerHeight,
hasSecondaryArea,
secondaryPosition,
secondarySize,
primaryParticipants,
secondaryParticipants,
participantsOrder,
particpantsMediaInformation,
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) {
// We have other videos
// 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 (hasSecondaryArea) {
// Todo
}
if (hasSelfView) {
// We have a self view, add the self video below the main video
const selfViewHeight = containerHeight - mainContainerTop - mainContainerHeight;
const selfViewWidth = selfViewHeight / threeToFour;
result.videoPositions.push({
participantId: "myself", // Note: the self view participant ID is always 'myself'
width: selfViewWidth,
height: selfViewHeight,
top: containerHeight - selfViewHeight,
left: (containerWidth / 2) - (selfViewWidth / 2),
zIndex: 1
});
}
return result;
}
}
window["wlvmrApiReady"] = (handler) => {
if (!window[handler]) {
console.error(`Handler window.${handler} not found!`);
return;
}
window[handler].setVideoDisplayCalculator(videoDisplayCalculator);
}
The following input and output types are used to interact with the Javascript API.
interface IVideoPosition {
participantId: string;
width: number;
height: number;
top: number;
left: number;
zIndex: number;
// Important: CSS class names MUST be prefixed with 'video-container-'
cssClasses?: string[]
}
interface IVideoDisplayConfig {
videoPositions: IVideoPosition[],
hiddenContainers: boolean[]
}
export interface IMediaInformation {
audioMuted?: boolean;
videoMuted?: boolean;
hasAudio?: boolean;
hadAudio?: boolean;
offersVideo?: boolean;
hasVideo?: boolean;
hadVideo?: boolean;
videoWidth?: number;
videoHeight?: number;
}
export interface IParticipantsMediaInformation {
[participantId: string]: IMediaInformation
}
interface IVideoDisplayCalculator {
calculatePositions(
containerWidth: number,
containerHeight: number,
hasSecondaryArea: boolean,
secondaryPosition: SecondaryVideoPosition,
secondarySize: SecondaryVideoSize,
primaryParticipants: string[],
secondaryParticipants: string[],
participantsOrder: string[],
particpantsMediaInformation: IParticipantsMediaInformation,
hasSelfView: boolean,
isSelfviewInSecondary: boolean,
isTVMode: boolean
): IVideoDisplayConfig;
}
enum SecondaryVideoSize {
small = 0.15,
large = 0.5
}
type SecondaryVideoPosition = "none" | "left" | "top" | "right" | "bottom";
Contact our team to discuss the details.