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. Alles, was auf jener Seite dazu steht, wie Sie den Handler erhalten, gilt deshalb auch hier.

BegriffBedeutung
VideoDer Videostream eines Teilnehmers
ContainerDer Bereich des Meetingraums, in dem der Raum alle Videos darstellt

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. 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:

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.

Primary and secondary area

Der sekundäre Bereich kann an jeder Kante liegen und kommt in zwei Grössen:

WertBedeutung
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, 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.

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 den Raum umstellt. Ihre Funktion muss synchron zurückkehren.

Der Meetingraum hat 12 Videocontainer. Sie entscheiden, welche davon Sie nutzen und welche verborgen bleiben.

Was Sie erhalten

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

PositionParameterTypBedeutung
1containerWidthnumberBreite des Videocontainers, in Pixeln
2containerHeightnumberHöhe des Videocontainers, 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 Videomasse 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 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:

EigenschaftTypBedeutung
participantIdstringWessen Video in diesen Container 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 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.

Beispiel

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

Example video display

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

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

  • Eine Position hinzufügen und den zugehörigen Eintrag in hiddenContainers 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.
  • Eine Fehlermeldung erwarten, wenn der Calculator scheitert. Er fällt stillschweigend auf das eingebaute Layout zurück.
  • 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 er 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.