Mit dem Video Display Calculator ordnen Sie die Teilnehmervideos selbst an. Statt unser Layout zu übernehmen, schreiben Sie eine Funktion. Diese bekommt die Containergrösse und die Teilnehmer und gibt für jedes Video eine absolute Position zurück.
Der Calculator gehört zur JavaScript-API. Alles, was auf jener Seite dazu steht, wie Sie den Handler erhalten, gilt deshalb auch hier.
| Begriff | Bedeutung |
|---|---|
| Video | Der Videostream eines Teilnehmers |
| Container | Der Bereich des Meetingraums, in dem der Raum alle Videos darstellt |
Wenn Sie mit einem KI-Coding-Assistenten arbeiten, können Sie den folgenden Text in Ihren Assistenten kopieren und mit einem Satz ergänzen, der beschreibt, was Sie programmieren möchten.
Der Prompt verweist auf unsere Entwicklerdokumentation, die wir für Coding-Assistenten in einer separaten, token-optimierten KI-Version ausliefern.
Lies zuerst diese Seite, bevor Du Code schreibst:
https://www.veeting.com/de/developer-documentation/video-display-calculator
Sie beschreibt, wie ich die Videoanordnung in einem Veeting Rooms
Meetingraum übernehme. Halte Dich an diese fünf Regeln:
1. videoPositions und hiddenContainers hängen über den Index
zusammen. Eine Position, deren Eintrag in hiddenContainers noch
true ist, erscheint NICHT. Setze ihn für jeden befüllten
Container auf false.
2. Es gibt 12 Container. Gib nicht mehr zurück.
3. Der Self-View steht in keinem Teilnehmer-Array. Ist
hasSelfView true und will ich ihn sehen, ergänze eine Position
für die participantId "myself".
4. Die Funktion muss synchron zurückkehren. Keine Promises, kein
await.
5. Wirft sie einen Fehler, fällt der Raum stillschweigend auf sein
eigenes Layout zurück. Baue deshalb eine Fehlerbehandlung ein,
die ich auch sehe.
Was ich bauen möchte:Das Präsentationslayout teilt den Container in zwei Bereiche. Das klassische Layout kennt keinen sekundären Bereich. Dort füllt der primäre Bereich immer den ganzen Container.
Standardmässig liegt jedes Video im primären Bereich, und dieser nimmt den ganzen Container ein. Ein Moderator kann Videos in den sekundären Bereich schieben. Sobald dort ein Video liegt, teilt sich der Container.

Der sekundäre Bereich kann an jeder Kante liegen und kommt in zwei Grössen:
| Wert | Bedeutung |
|---|---|
secondaryPosition | none, left, top, right oder bottom |
secondarySize | 0.15 für den kleinen, 0.5 für den grossen Bereich, als Anteil des Containers |
Sie bekommen beide Werte. Ob Sie sie beachten, entscheiden Sie selbst, und wie Sie den Platz aufteilen, ebenfalls. Wir liefern Ihnen die Masse des gesamten Containers in Pixeln, nicht die der einzelnen Bereiche.
Der Meetingraum ruft Ihre Funktion immer dann auf, wenn er die Positionen neu berechnen muss: wenn jemand beitritt oder geht, wenn sich die Fenstergrösse ändert, wenn der Moderator den Raum umstellt. Ihre Funktion muss synchron zurückkehren.
Der Meetingraum hat 12 Videocontainer. Sie entscheiden, welche davon Sie nutzen und welche verborgen bleiben.
Die Parameter zählen nach Position, nicht nach Namen. Die Namen wählen Sie also selbst. In dieser Reihenfolge kommen sie an:
| Position | Parameter | Typ | Bedeutung |
|---|---|---|---|
| 1 | containerWidth | number | Breite des Videocontainers, in Pixeln |
| 2 | containerHeight | number | Höhe des Videocontainers, in Pixeln |
| 3 | hasSecondaryArea | boolean | Ob der sekundäre Bereich sichtbar ist |
| 4 | secondaryPosition | SecondaryVideoPosition | An welcher Kante der sekundäre Bereich liegt |
| 5 | secondarySize | SecondaryVideoSize | 0.15 oder 0.5 |
| 6 | primaryParticipants | string[] | Teilnehmer-IDs im primären Bereich |
| 7 | secondaryParticipants | string[] | Teilnehmer-IDs im sekundären Bereich |
| 8 | participantsOrder | string[] | Alle Teilnehmer-IDs, in der Reihenfolge der Anzeige |
| 9 | participantsMediaInformation | IParticipantsMediaInformation | Medienzustand und Videomasse pro Teilnehmer |
| 10 | hasSelfView | boolean | Ob es einen Self-View gibt |
| 11 | isSelfviewInSecondary | boolean | Ob der Self-View in den sekundären Bereich gehört |
| 12 | isTVMode | boolean | Ob der Raum im TV-Modus läuft |
Der Self-View steht in keinem Teilnehmer-Array. Ist
hasSelfViewgleichtrueund wollen Sie den Self-View zeigen, dann ergänzen Sie selbst eine Position für die Teilnehmer-IDmyself. Sonst tut es niemand.
| Eigenschaft | Typ | Bedeutung |
|---|---|---|
| videoPositions | IVideoPosition[] | Ein Eintrag je Video, das der Raum darstellen soll |
| hiddenContainers | boolean[] | 12 Einträge. false zeigt den Container an dieser Position, true verbirgt ihn. |
Die beiden Arrays hängen über den Index zusammen: Der erste Eintrag von videoPositions gilt für Container 0, der zweite für Container 1 und so weiter. Eine Position, deren hiddenContainers[i] auf true bleibt, erscheint nicht. Das ist hier der mit Abstand häufigste Fehler.
Eine IVideoPosition sieht so aus:
| Eigenschaft | Typ | Bedeutung |
|---|---|---|
| participantId | string | Wessen Video in diesen Container gehört, oder myself für den Self-View |
| width | number | Breite in Pixeln |
| height | number | Höhe in Pixeln |
| top | number | Abstand vom oberen Rand des Containers, in Pixeln |
| left | number | Abstand vom linken Rand des Containers, in Pixeln |
| zIndex | number | Stapelreihenfolge, falls Sie Videos überlappen lassen |
| cssClasses | string[] | Optional. Jede Klasse muss mit video-container- beginnen. |
Wirft er einen Fehler oder gibt er ein leeres Objekt zurück, fällt der Meetingraum stillschweigend auf das eingebaute Layout zurück. Niemand bekommt eine Fehlermeldung zu sehen. Erscheint Ihr Layout also nicht, schauen Sie zuerst in der Browserkonsole nach.
Das folgende Beispiel setzt den ersten Teilnehmer in die Mitte, die übrigen in eine Reihe darüber und den Self-View darunter.

Es nutzt nur den primären Bereich.
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 auf true stehen lassen. Das Video erscheint dann nie.myself ergänzen.video-container- beginnt.secondarySize sei immer 0.15. Beim grossen sekundären Bereich ist er 0.5.Sprechen Sie mit unserem Team über Ihre Pläne.