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. Was dort zum Erhalten des Handlers steht, gilt auch hier.
| Begriff | Bedeutung |
|---|---|
| Video | Der Videostream eines Teilnehmers |
| Container | Der Bereich des Meetingraums, in dem alle Videos dargestellt werden |
| Videocontainer | Einer der 12 Plätze im Container. Jeder nimmt ein Video auf. |
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. Jede Position in videoPositions landet über ihre participantId
im Container dieses Teilnehmers, am Index des Teilnehmers in
participantsOrder. Die Reihenfolge von videoPositions spielt
keine Rolle. Steht hiddenContainers[i] noch auf true, erscheint
das Video von participantsOrder[i] NICHT. Den Self-View (myself)
steuert hiddenContainers nicht.
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 es gibt ihn in zwei Grössen:
| Parameter | 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 und wie Sie den Platz aufteilen, entscheiden Sie selbst. 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 die Videos neu anordnet. Ihre Funktion muss synchron zurückkehren.
Der Meetingraum hat 12 Videocontainer. Sie entscheiden, welche davon Sie nutzen und welche verborgen bleiben. Registrieren Sie Ihren Calculator mit setVideoDisplayCalculator, wie am Ende des Beispiels unten.
Die Parameter werden nach Position übergeben, nicht nach Namen. Die Namen wählen Sie also selbst. In dieser Reihenfolge kommen sie an:
| Reihenfolge | Parameter | Typ | Bedeutung |
|---|---|---|---|
| 1 | containerWidth | number | Breite des Containers, in Pixeln |
| 2 | containerHeight | number | Höhe des Containers, 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 Videoabmessungen 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 Videocontainer an diesem Index, true verbirgt ihn. |
Jeder Eintrag in videoPositions landet über seine participantId im Videocontainer dieses Teilnehmers. Dessen Index ist der Index des Teilnehmers in participantsOrder; die Reihenfolge von videoPositions spielt keine Rolle. hiddenContainers[i] zeigt oder verbirgt den Container von participantsOrder[i]. Positionieren Sie einen Teilnehmer, lassen aber seinen Eintrag in hiddenContainers auf true, erscheint sein Video nicht. Das ist hier der mit Abstand häufigste Fehler.
Der Self-View (myself) ist ein eigenes Element und keiner der 12 Videocontainer. hiddenContainers steuert ihn nicht: Er erscheint, sobald hasSelfView auf true steht und Sie eine Position für myself zurückgeben.
Eine IVideoPosition sieht so aus:
| Eigenschaft | Typ | Bedeutung |
|---|---|---|
| participantId | string | Wessen Video in diesen Videocontainer 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 Ihr Calculator einen Fehler, fällt der Meetingraum auf das eingebaute Layout zurück und schreibt den Fehler in die Browserkonsole. Im Meeting selbst erscheint nichts. Bleibt Ihr Layout also aus, öffnen Sie die Konsole und suchen Sie nach «Error in custom video display calculator». Gibt Ihr Calculator gar nichts zurück, greift derselbe Fallback, aber ohne Logeintrag: Eine leere Konsole heisst also nicht, dass Ihr Calculator gelaufen ist.
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
});
}
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 (am Index des Teilnehmers in participantsOrder) 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 der Wert 0.5.Sprechen Sie mit unserem Team über Ihre Pläne.