WebController - Enter Frame

The enter frame section contains mostly methods that are triggered each frame or in a similar way.



The onNewFrame method performs various tasks that need constant updating. We call it every frame for the Genvid client we created.

  1. First we update the overlays to adapt to the composition of the video stream through updateOverlays(compositionData). There, we check the received composition data for the number of sources and composition data layout types. It enables us to update the overlay accordingly for multi-source(ChromaKey/PiP) and single source streaming.

  2. We then call updateStreamsInfoFromSession(session) to extract the game data streams out of the given frame. The extracted data, devided in strams such as Positions, Colors, and Camera is used to update the web page overlay.

  3. Finally, we query the GenvidClient to update the UI with such information as the latency, the delay offset, volume visibility, etc.



The onStreamsReceived method uses loops to get the game data. We check the game data for its format. If the format is UTF-8, we check the stream id to make sure the character string we received is the copyright data and, if so, we display it in the console. We expect the rest of the data streams to be formated in JSON. In such a case, we look for the “Names” stream. Once found, we can pass the names data as a parameter to initPlayerTable(cubeNames) so that the info pannel can be constructed. Then, we proceed to check the streams data for annotations. If we find a JSON formatted annotation with a “Colors” id, we extract the data to update the overlay about which cubes changed color.



The initPlayerTable creates a table under the stream window for each object.

  1. We start with a loop that repeats for each object’s information, based on received game data.

  2. We clone a node that we later append to an HTML tag.

  3. We then add the event listeners for the table, cheer, and reset click functionality.

  4. Finally, we perform a loop for each color available and add a click-event listener.

      // Method used to display the appropriate number of players with their proper buttons
      initPlayerTable(cubeNames) {
        const cubePanel = document.getElementById("cube_panel_prototype");
        // The prototype panel gets removed after init.
        if (cubePanel === null) {
          // We already have real pannels. No need for more.
        this.cubeNames = cubeNames;
        for (const idx in cubeNames) {
          const name = this.cubeNames[idx];
          let cubePanelClone = cubePanel.cloneNode(true);
          cubePanelClone.id = idx;
          cubePanelClone.getElementsByClassName("cube_name")[0].innerText = name;
          let cheerButton = cubePanelClone.querySelector(".cheer");
          cheerButton.addEventListener("click", () => this.onCheer(name), false);
          let cubeDiv = cubePanelClone.querySelector(".cube");
            () => this.selectCube(parseInt(idx)),
          this.cubePanelDiv.push(cubeDiv); // will stop triggering initPlayerTable from onNewFrame() indefinitely
          let resetButton = cubePanelClone.querySelector(".reset");
          resetButton.addEventListener("click", () => this.onReset(name), false);
          for (let colorSelect of this.tableColor) {
            let colorButton = cubePanelClone.querySelector("." + colorSelect[0]);
              () => this.onColorChange(name, colorSelect[1]),
          const panel = {
            panel: cubePanelClone,
            x: cubePanelClone.getElementsByClassName("position_x")[0],
            y: cubePanelClone.getElementsByClassName("position_y")[0],
            z: cubePanelClone.getElementsByClassName("position_z")[0],
            popularity: cubePanelClone.getElementsByClassName("cheer_value")[0],
        // We don't need the prototype panel anymore. We now have real panels.



The onNotificationsReceived method checks if any notification’s ID is Popularity. When it is, we convert the JSON data into JavaScript and update the overlay. We call this method when the website receives a notification.

  // Upon receiving a notification, gets the notification content
  onNotificationsReceived(message) {
    for (let notification of message.notifications) {
      if (notification.id == "POPULARITY") {
        let data = JSON.parse(notification.data);
        this.popularities = data.popularity;
      } else { // Don't spam popularity.
        genvid.log("notification received: ", notification.data);
        document.querySelector("#alert_notification").style.visibility = "visible";
        document.querySelector("#notification_message").textContent = notification.data;
        if (this.showNotificationTimeoutID !== 0) {
        this.showNotificationTimeoutID = setTimeout(() => {
          document.querySelector("#alert_notification").style.visibility = "hidden";
          this.showNotificationTimeoutID = 0



The render function uses the game data provided to update the scene. First, we check if we received the cube names. If so, we create the corresponding tags and circle sprites. We only do so once. We then use received camera data, cube positions data, and Three.js functionality to update the sprite and tag position in 2D space to visually match the postions of the cubes in the rendered frame. The position info is also used to update the player pannels. Next, we call the Three.js renderer to render the updated scene. Finally, we call requestAnimationFrame with render as the argument to keep updating the scene.