WebController - User Interactions

This section contains all the methods used for the User interactions in the index.html web page. All the key-press functions and click functions are then available in this section.

onKeyDown

onKeyDown(event)

The onKeyDown method triggers a specific function depending on which key is pressed while the window is selected.

changeDelayOffset
Changes the video delay offset by changing the value.
toggleGenvidOverlay
Displays or hides the Genvid overlay.
toggleFullscreen
Toggles the fullscreen on and off.
genvidClient.videoPlayer.isPaused
Returns a boolean indicating if the video is paused.
genvidClient.videoPlayer.play
Plays the video.
genvidClient.videoPlayer.pause
Pauses the video.
genvidClient.videoPlayer.getMuted
Returns a boolean indicating if the video sound is muted.
genvidClient.videoPlayer.setMuted
Sets the sound to mute or unmute depending of the argument.
genvidClient.videoPlayer.setVolume
Sets the video volume to the sent argument.
genvidClient.videoPlayer.getVolume
Gets the current video volume.
onHelpActivation
Displays or hides the help overlay.
    onKeyDown(event) {
        // Seems to be the most cross-platform way to determine keys:
        // Ref: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code
        let code = event.code || this.getKeyCode(event);
        switch (code) {
            case "Equal":
            case "NumpadAdd":
                this.changeDelayOffset(+1, event);
                break;
            case "Minus":
            case "NumpadSubtract":
                this.changeDelayOffset(-1, event);
                break;
            case "NumpadMultiply":
                this.changeDelayOffset(0, event);
                break;
            case "KeyG":
                this.toggleGenvidOverlay();
                break;
            case "KeyF":
                this.toggleFullScreen();
                break;
            case "Space":
                if (this.genvidClient.videoPlayer.isPaused()) {
                    this.genvidClient.videoPlayer.play();
                } else {
                    this.genvidClient.videoPlayer.pause();
                }
                event.preventDefault();
                break;
            case "KeyM":
                this.toggleMute();
                break;
            case "KeyZ":
                this.decreaseVolume();
                break;
            case "KeyX":
                this.increaseVolume();
                break;
            case "KeyH":
                this.onHelpActivation();
                break;
        }
    }

getKeyCode

getKeyCode(event)

The getKeyCode method gets a proper keyCode on different browsers.

    // Compatibility code for browsers (Safari) not having KeyboardEvent.code.
    getKeyCode(event) {
        if (event.keyCode) {
            if (65 <= event.keyCode && event.keyCode <= 90) {
                return "Key" + String.fromCharCode(event.keyCode);
            } else {
                switch (event.keyCode) {
                    case 13:
                        return "Enter";
                    case 106:
                        return "NumpadMultiply";
                    case 107:
                        return "NumpadAdd";
                    case 109:
                        return "NumpadSubtract";
                    case 110:
                        return "NumpadDecimal";
                    case 111:
                        return "NumpadDivide";
                    case 187:
                        return "Equal";
                    case 188:
                        return "Comma";
                    case 189:
                        return "Minus";
                    case 190:
                        return "Period";
                    case 222:
                        return "Backquote";
                }
            }
        }
    }

onResize

onResize()

The onResize method resizes the various overlays used in the web page for index.html. The overlays affected by this function are:

  • videoOverlay,
  • canvas3d,
  • volumeDisplay (used for sound modification)
  • the animated divs with the name of each object.

We get the size value of the video stream displayed in the index.html page and we adjust the other overlays according to this size. In this sample, resize is essential because we need the proper overlay size to perform the object selection properly. Also, we need it to display the WebGL circles at their appropriate locations to follow the objects in the video.

    // Allows to adjust the various overlays when resizing the windows - needed to see the PromptOverlay and Name moving div
    onResize() {
        const refElement = this.genvidClient.videoElem; // The element to match.
        const refElementSize = refElement ? genvidMath.vec2(refElement.clientWidth, refElement.clientHeight) : genvidMath.vec2(1280, 720);
        const refElementRatio = refElementSize.x / refElementSize.y;
        const videoRatio = this.genvidClient.videoAspectRatio;
        let pos;
        let size;
        if (videoRatio >= refElementRatio) {
            // Top+Bottom bars, fills width fully, shrinks height.
            const ey = refElementSize.x / videoRatio;
            const dy = refElementSize.y - ey;
            // Centers vertically.
            const y = dy * 0.5;
            pos = genvidMath.vec2(0, Math.round(y));
            size = genvidMath.vec2(refElementSize.x, Math.round(ey));
        } else {
            // Left+Right bars, fills height fully, shrinks width.
            const ex = refElementSize.y * videoRatio;
            const dx = refElementSize.x - ex;
            // Centers horizontally.
            const x = dx * 0.5;
            pos = genvidMath.vec2(Math.round(x), 0);
            size = genvidMath.vec2(Math.round(ex), refElementSize.y);
        }
        const style = this.videoOverlay.style;
        const curPos = genvidMath.vec2(parseInt(style.left), parseInt(style.top));
        const curSize = genvidMath.vec2(parseInt(style.width), parseInt(style.height));

        if (!genvidMath.equal2D(curSize, size, 0.9) || !genvidMath.equal2D(curPos, pos, 0.9)) {
            this.videoOverlay.style.left = pos.x + "px";
            this.videoOverlay.style.width = size.x + "px";
            this.videoOverlay.style.top = pos.y + "px";
            this.videoOverlay.style.height = size.y + "px";
            this.canvas3d.width = size.x;
            this.canvas3d.height = size.y;
            this.genvidWebGL.setViewport(0, 0, size.x, size.y); // Renders in the whole area.
        }
    }

toggleFullScreen

toggleFullScreen()

The toggleFullScreen method activates or deactivates fullscreen on the video. We use the checkFullScreen function to verify fullscreen status and adjust to the proper condition.

If we’re already in fullscreen, we cancel fullscreen according to the proper web browser function and we also adjust the fullscreen button icon.

If we aren’t in fullscreen, we proceed to get the video_area element. Afterwards, we use the proper web browser function to activate fullscreen and update the fullscreen button icon.

    toggleFullScreen() {
        if (this.checkFullScreen()) {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            }
            this.fullScreenIcon.classList.remove("fa-compress");
            this.fullScreenIcon.classList.add("fa-expand");
        } else {
            let element = document.querySelector("#video_area");
            if (element.requestFullscreen) {
                element.requestFullscreen();
            } else if (element.mozRequestFullScreen) {
                element.mozRequestFullScreen();
            } else if (element.webkitRequestFullscreen) {
                element.webkitRequestFullscreen();
            } else if (element.msRequestFullscreen) {
                element.msRequestFullscreen();
            }
            this.fullScreenIcon.classList.remove("fa-expand");
            this.fullScreenIcon.classList.add("fa-compress");
        }
    }

toggleMute

toggleMute()

The toggleMute method is called whenever the video player is muted/unmuted.

Whenever this function is invoked, we can retrieve the mute state by calling getMuted(), and adjust the visibility of the audio, volume up icons, and the overlay message appropriately.

    // Depending on the status, mutes or unmutes the video player audio
    toggleMute() {
        const muteIcon = document.querySelector("#mute-button i");
        if (this.genvidClient.videoPlayer.getMuted()) {
            muteIcon.classList.remove("fa-volume-off");
            muteIcon.classList.add("fa-volume-up");
            this.genvidClient.videoPlayer.setMuted(false);
            this.volumeDisplay.style.visibility = "visible";
            this.volumeDisplay.textContent = "Volume is unmuted";
            this.volumeInfoDisplayCount = 0;
        } else {
            muteIcon.classList.remove("fa-volume-up");
            muteIcon.classList.add("fa-volume-off");
            this.genvidClient.videoPlayer.setMuted(true);
            this.volumeDisplay.style.visibility = "visible";
            this.volumeDisplay.textContent = "Volume is muted";
            this.volumeInfoDisplayCount = 0;
        }
    }

decreaseVolume increaseVolume

decreaseVolume() increaseVolume()

The decreaseVolume and increaseVolume methods use genvidClient.videoPlayer.setVolume and adjust the volumeDisplay overlay accordingly.

    decreaseVolume() {
        const newVolume = this.genvidClient.videoPlayer.getVolume() - 20;
        this.genvidClient.videoPlayer.setVolume(newVolume);
        this.volumeDisplay.style.visibility = "visible";
        this.volumeInfoDisplayCount = 0;
        this.volumeDisplay.textContent = `Volume: ${this.genvidClient.videoPlayer.getVolume()} %`;
    }

    increaseVolume() {
        const newVolume = this.genvidClient.videoPlayer.getVolume() + 20;
        this.genvidClient.videoPlayer.setVolume(newVolume);
        this.volumeDisplay.style.visibility = "visible";
        this.volumeInfoDisplayCount = 0;
        this.volumeDisplay.textContent = `Volume: ${this.genvidClient.videoPlayer.getVolume()} %`;
    }

showOverlay

showOverlay()

The showOverlay method displays the Genvid overlay.

    // Changes the style to display the Genvid overlay
    showOverlay() {
        this.genvidOverlay.style.display = "block";
    }

hideOverlay

hideOverlay()

The hideOverlay method hides the Genvid overlay.

    // Changes the style to hide the Genvid overlay
    hideOverlay() {
        this.genvidOverlay.style.display = "none";
    }

clickCube

clickCube(event)

The clickCube function returns true if the pickCube method determines that a cube was selected by clicking the WebGL overlay. If not, it returns false.

    // Method used when clicking on the WebGL overlay
    clickCube(event) {
        return !!this.pickCube(event);
    }

pickCube

pickCube(event)

The pickCube method determines if a cube is selected by clicking on the WebGL overlay.

  1. We verify that the game data is valid, otherwise we are not doing any verification.

  2. We get the position of the click in the window.

  3. Then we convert this coordinate into the projected space.

            if (this.lastGameData == null) {
                return false;
            }
    
            // [0, 1] coordinates in the window.
            const rect = this.canvas3d.getBoundingClientRect();
            const x = event.pageX - rect.left; // More robust to recompute from pageX/pageY.
            const y = event.pageY - rect.top;
            const p01 = genvidMath.vec2(x / rect.width, y / rect.height); // viewport click coord between 0 and 1
            // [-1, 1] coordinates in projection space.
            const p = genvidMath.mad2D(p01, genvidMath.vec2(2, -2), genvidMath.vec2(-1, 1));
            const mat = this.convertMatrix(this.lastGameData.MatProjView); //generates proj view matrix
            let best = null;
            let bestDist = Infinity;
    
  4. We start a loop to verify the coordinates of each object.

  5. In this loop, we get the object coordinates and apply a radius to have a zone to search.

  6. Finally, we search to discover if the coordinates are within the object zone and keep it as the best result based on the distance. (In the event that two objects are close, the one with nearer to the zone is selected).

            for (let cube of this.lastGameData.cubes) {
                let pos3D = genvidMath.vec3(cube.mat[12], cube.mat[13], cube.mat[14]);
                let pos2DRad2D = this.projectWithRadius(mat, pos3D, 1.0);
                let pos2D = pos2DRad2D[0];
                let rad2D = pos2DRad2D[1];
    
                let pos2DToP = genvidMath.sub2D(p, pos2D);
                let d = genvidMath.length2D(pos2DToP);
                if (d < rad2D && d < bestDist) {
                    best = cube.name;
                    bestDist = d;
                }
            }
    
            this.onSelect(best || "");
            return best;
    

isSelected

isSelected(name)

The isSelected method tells us if an object is selected or not.

    // Verifies if the cube is selected
    isSelected(name) {
        return this.selectedCubeName === name;
    }

changeDelayOffset

changeDelayOffset(direction, event)

The changeDelayOffset method adds, reduces, or resets the delay offset of the video. If the argument direction is equal to 0, the delay offset of the video is reset. Otherwise, it is changed according to the value sent.

    // Function that changes the delay offset depending of the key pressed
    changeDelayOffset(direction, event) {
        if (direction !== 0) {
            let delayDelta = 100 * direction;
            if (event.altKey) delayDelta *= 10;
            if (event.shiftKey) delayDelta /= 3;
            this.genvidClient.delayOffset += delayDelta;;
        } else {
            this.genvidClient.delayOffset = 0;
        }
    }

toggleGenvidOverlay

toggleGenvidOverlay()

The toggleGenvidOverlay method displays or hides the Genvid overlay. The Genvid overlay displays various stats about the stream.

Local
Local time of the machine displaying the webpage.
Est. Video
Estimated time when the video is received.
Stream received
Time of the last data reported by the Compose service.
Stream played
Current time of the generalized stream.
Latency
Estimated latency between the game and the spectators.
DelayOffset
Optional delay offset used for minute synchronization adjustments.
    // Displays or removes the Genvid Overlay
    toggleGenvidOverlay() {
        if (this.genvidOverlay.getAttribute("data-isHidden")) {
            this.genvidOverlay.setAttribute("data-isHidden", "");
            this.genvidOverlay.style.visibility = "visible";
            this.genvidOverlayButton.classList.remove("disabled");
        } else {
            this.genvidOverlay.setAttribute("data-isHidden", "true");
            this.genvidOverlay.style.visibility = "hidden";
            this.genvidOverlayButton.classList.add("disabled");
        }
    }

onHelpActivation

onHelpActivation()

The onHelpActivation method displays or hides the help overlay.

    // Displays or removes the help overlay
    onHelpActivation() {
        if (this.helpOverlay.style.visibility === "visible") {
            this.helpOverlay.style.visibility = "hidden";
        } else {
            this.helpOverlay.style.visibility = "visible";
        }
    }

onCheer

onCheer(cubeName)

The onCheer method sends and event to the client when a button is clicked to cheer a player. The event contains the name of the object.

    // Upon cheering a player
    onCheer(cubeName) {
        this.genvidClient.sendEventObject({
            cheer: cubeName
        });
    }

onReset

onReset(cubeName)

The onReset method sends an event when a button is clicked to reset a player. The event contains the name of the object.

    // Resets the position of the cube
    onReset(cubeName) {
        this.genvidClient.sendEventObject({
            reset: cubeName
        });
    }

onColorChange

onColorChange(cube, color)

The onColorChange method sends an event when a button is clicked on to change the color of a player. The event contains the name of the object and the color.

    // Method used when clicking on a color to change the color of a cube
    onColorChange(cube, color) {
        let evt = {
            key: ["changeColor", cube],
            value: color,
        };
        this.genvidClient.sendEvent([evt]);
    }

onSelect

onSelect(cubeName, selectionInterface)

The onSelect method changes the UI when the user clicks the player table located under the video stream. We reset the color for each table and apply the color to the selected table only, then we select the WebGL circle.

    // Selects the cube from the interface
    onSelect(cubeName) {

        for (let nameSelect of this.cubePanelDiv) {
            nameSelect.style.backgroundColor = "#181818";
        }

        if (cubeName) {
            let cubeDiv = document.querySelector(`#${cubeName} .cube`);
            if (cubeDiv) {
                cubeDiv.style.backgroundColor = "#32324e";
            }
        }

        this.selectedCubeName = cubeName;
    }