Kontrolle über die Videodarstellung

Übersicht

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.

BegriffBedeutung
VideoDer Videostream eines Teilnehmers
ContainerDer Bereich des Meetingraums, in dem alle Videos dargestellt werden
VideocontainerEiner der 12 Plätze im Container. Jeder nimmt ein Video auf.

Verwendung mit einem KI-Assistenten

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:

Der primäre und der sekundäre Bereich

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.

Primärer und sekundärer Bereich im Meetingraum

Der sekundäre Bereich kann an jeder Kante liegen, und es gibt ihn in zwei Grössen:

ParameterBedeutung
secondaryPositionnone, left, top, right oder bottom
secondarySize0.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.

Den Calculator schreiben

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.

Was Sie erhalten

Die Parameter werden nach Position übergeben, nicht nach Namen. Die Namen wählen Sie also selbst. In dieser Reihenfolge kommen sie an:

ReihenfolgeParameterTypBedeutung
1containerWidthnumberBreite des Containers, in Pixeln
2containerHeightnumberHöhe des Containers, in Pixeln
3hasSecondaryAreabooleanOb der sekundäre Bereich sichtbar ist
4secondaryPositionSecondaryVideoPositionAn welcher Kante der sekundäre Bereich liegt
5secondarySizeSecondaryVideoSize0.15 oder 0.5
6primaryParticipantsstring[]Teilnehmer-IDs im primären Bereich
7secondaryParticipantsstring[]Teilnehmer-IDs im sekundären Bereich
8participantsOrderstring[]Alle Teilnehmer-IDs, in der Reihenfolge der Anzeige
9participantsMediaInformationIParticipantsMediaInformationMedienzustand und Videoabmessungen pro Teilnehmer
10hasSelfViewbooleanOb es einen Self-View gibt
11isSelfviewInSecondarybooleanOb der Self-View in den sekundären Bereich gehört
12isTVModebooleanOb der Raum im TV-Modus läuft

Der Self-View steht in keinem Teilnehmer-Array. Ist hasSelfView gleich true und wollen Sie den Self-View zeigen, dann ergänzen Sie selbst eine Position für die Teilnehmer-ID myself. Sonst tut es niemand.

Was Sie zurückgeben

EigenschaftTypBedeutung
videoPositionsIVideoPosition[]Ein Eintrag je Video, das der Raum darstellen soll
hiddenContainersboolean[]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:

EigenschaftTypBedeutung
participantIdstringWessen Video in diesen Videocontainer gehört, oder myself für den Self-View
widthnumberBreite in Pixeln
heightnumberHöhe in Pixeln
topnumberAbstand vom oberen Rand des Containers, in Pixeln
leftnumberAbstand vom linken Rand des Containers, in Pixeln
zIndexnumberStapelreihenfolge, falls Sie Videos überlappen lassen
cssClassesstring[]Optional. Jede Klasse muss mit video-container- beginnen.

Wenn Ihr Calculator scheitert

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.

Beispiel

Das folgende Beispiel setzt den ersten Teilnehmer in die Mitte, die übrigen in eine Reihe darüber und den Self-View darunter.

Beispiel einer Videodarstellung mit eigenem Layout

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);
};

Typdefinitionen

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

Häufige Fehler

  • Einen Teilnehmer positionieren und seinen Eintrag in hiddenContainers (am Index des Teilnehmers in participantsOrder) auf true stehen lassen. Das Video erscheint dann nie.
  • Den Self-View vergessen. Er steht in keinem Teilnehmer-Array, Sie müssen ihn als myself ergänzen.
  • Erwarten, dass der Raum den Fehler anzeigt. Er fällt auf das eingebaute Layout zurück und schreibt stattdessen in die Konsole.
  • Mehr als 12 Positionen zurückgeben.
  • Eine CSS-Klasse verwenden, die nicht mit video-container- beginnt.
  • Annehmen, secondarySize sei immer 0.15. Beim grossen sekundären Bereich ist der Wert 0.5.
  • Asynchron arbeiten. Die Funktion muss ihr Ergebnis synchron zurückgeben.

Sind Sie nicht sicher, wie Sie Ihr Projekt am Besten umsetzen sollen?

Sprechen Sie mit unserem Team über Ihre Pläne.