TeamFON Webmeeting Version 6.8 Erweiterung
{autotoc enabled=yes}
Video display calulator
Introduction
The Video Display Calculater 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.
Glossary
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 |
Primary and secondary display area
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)
Calculating video positions
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.
Input parameters
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 string myself to add the selfview to the output data structure.
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-' |
Error handling
If the video display calculator throws an error or returns an empty object the system will revert to the internal position calculator instead.
Example
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);
}
Type definitions
The following input and output types are used to interact with the Javascript API.
Interfaces
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;
}
Enums
enum SecondaryVideoSize { small = 0.15, large = 0.5}
Types
type SecondaryVideoPosition = "none" | "left" | "top" | "right" | "bottom";
Custom Tools
Overview
Custom tools allow integrators and developers to create custom tools within the meeting room. These tools consist of a label and an iFrame URL
Configuration
Custom tools are configured in the system settings of a white label instance. A white label instance can only have one custom tool configuration. By default, no custom tool is configured.
Custom tools can either be configured as a static iFrame URL or through an API endpoint.
iFrame URL
A static iFrame tool configuration consists of the iFrame URL and the tool label:
The iFrame must be served with HTTPS. Furthermore, the webserver must send the correct CORS headers to allow the browser to display the iFrame within the meeting room.
The label is either a fixed string or a pipe-separated string of translations. Examples:
Label | Explanation |
My Custom Tool | The label shown in the interface will always read “My Custom Tool”, independent of the user interface language of the web meeting. |
en:My Custom Tool|de-CH:Mein eigenes Tool|fr: Mon outil personnalisé | A user interface in English will show “My Custom Tool”, the user interface in German will show “Mein eigenes Tool”, etc. A user interface in Spanish (a language not defined in the label configuration) will either show “My Custom Tool” if an English label is available, or the label first defined in the list. |
The iFrame will be embedded in the meeting room with additional query parameters to allow the developers to customize the iFrame depending on these additional query parameters. The query parameters added to the iFrame URL are the following:
Query parameter name | Explanation |
participantId | The unique ID of the meeting participant |
participantName | The name of the participant |
meetingToken | The meeting ID in the form of “0000-0000-0000-0000” |
meetingId | The meeting ID in the form of “5349b4ddd2781d08c09890f3” |
moderatorToken | Optional, only if available for this specific user. |
culture | The locale code of the participant at the time when the participant enters the meeting room, for example “en” or “en-US” or “de-DE”, etc |
API
The API definition of the custom tool allows for more flexibility on the developer’s side. The developer can decide for each participant if a tool should be displayed or not, and which tool should be displayed. It is even possible to define multiple separate tools (up to 5).
The API tool needs to be configured in the system settings of a white label instance:
Once configured the web meeting server API will call this end point for each participant who joins the meeting to retrieve the list of tools which should be displayed to this specific participant.
The workflow is summaries as shown below:
The API request initiated by the web meeting server is an HTTP GET request with the following query parameters:
Query parameter name | Explanation |
participantId | The unique ID of the meeting participant |
participantName | The name of the participant |
meetingToken | The meeting ID in the form of “0000-0000-0000-0000” |
meetingId | The meeting ID in the form of “5349b4ddd2781d08c09890f3” |
moderatorToken | Optional, only if available for this specific user. |
culture | The locale code of the participant at the time when the participant enters the meeting room, for example “en” or “en-US” or “de-DE”, etc |
The API secret defined in the system configuration will be sent as an HTTP header “X-API-KEY”.
The API request expects an HTTP 200 response code and a JSON Array object consisting of a list of tool objects. A tool object is defined as:
Property name | Explanation |
iFrameUrl | An URL of an iFrame to be displayed |
toolIcon | SVG string |
labels | An array of “label” objects |
Whereas a label object is defined as
Property name | Explanation |
culture | The locale of the label, for example “en” or “en-US” or “de-DE”, etc |
label | String, the label displayed, e.g. “My Custom Tool” |
Example request:
curl 'https://<CUSTOM-TOOL-API-URL>?participantId=XXXXX\
&participantName=Joe%20Doe&meetingId=0000-0000-0000-0000\
&meetingToken=YYYYY&culture=en' \
-H 'X-API-KEY: <CUSTOM-TOOL-API-Key>' \
-H 'accept: application/json, text/plain, */*'
Example response:
[
{
"iFrameUrl": "https://www.example.com/custom-tool-1",
"toolIcon": "<svg>…</svg>",
"labels": [
{
"culture": "en-US",
"label": "Custom tool 1"
},
{
"culture": "de",
"label": "Spezialtool 1"
}
]
}
]