# Video display calculator

Source: https://www.veeting.com/en/developer-documentation/video-display-calculator

## Overview

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](/en/developer-documentation/javascript-apis), 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 |

## The primary and secondary areas

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.

![Primary and secondary area](/assets/img/documentation/video-display-calculator-areas.png)

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.

## Writing the calculator

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.

### What you receive

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 `hasSelfView` is `true` and you want to show it, add a position for the participant ID `myself`. Nothing else adds it for you.

### What you return

| 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 fails

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.

## Example

This places the first participant in the center, the others in a row above, and the self view below.

![Example video display](/assets/img/documentation/video-display-calculator-example.png)

It uses only the primary area.

```javascript
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 definitions

```typescript
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;
}
```

## Common mistakes

- Pushing a position but leaving its `hiddenContainers` entry at `true`, so the video never appears.
- Forgetting the self view, which is in no participant array and must be added as `myself`.
- Expecting an error when the calculator throws. The room falls back silently to the built-in layout.
- Returning more than 12 positions.
- Using a CSS class that does not start with `video-container-`.
- Assuming `secondarySize` is always `0.15`. It is `0.5` for the large secondary area.
- Doing asynchronous work. The function must return a result synchronously.

---

## The rest of this documentation

- [Custom tools](https://www.veeting.com/en/developer-documentation/custom-tools)
- [External meeting authorization service](https://www.veeting.com/en/developer-documentation/external-meeting-authorization-service)
- [iFrame and Web Components](https://www.veeting.com/en/developer-documentation/iframe-and-web-components)
- [JavaScript APIs](https://www.veeting.com/en/developer-documentation/javascript-apis)
- [Query parameters](https://www.veeting.com/en/developer-documentation/query-parameters)
- [Veeting Blocks - Components](https://www.veeting.com/en/veeting-blocks/components)
- [Veeting Blocks - Introduction](https://www.veeting.com/en/veeting-blocks/introduction)
- [Veeting Blocks - JavaScript and Typescript APIs](https://www.veeting.com/en/veeting-blocks/apis)
- [Veeting Rooms REST APIs](https://www.veeting.com/en/developer-documentation/api-usage)
- [Web hooks](https://www.veeting.com/en/developer-documentation/web-hooks)

All of it in one file: https://www.veeting.com/llms-full.txt
