unityController - Enter Frame

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

on_new_frame

on_new_frame(frameSource: genvid.IDataFrame)

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

  1. We get the annotation data first, verify that it’s valid, and display a console log afterwards.

  2. We need to get the game data and verify that this data is valid before doing any operation with it.

  3. Once we are certain about the data validity, we get the object list from the data (cubes).

  4. We update the text field for the time left until the next camera and scene.

  5. We verify if we set up the player table. If that isn’t the case, we call initPlayerTable.

  6. We use the objects array to create a loop that creates the circles made in WebGL.

  7. With the same loop, we update the popularity value and the position value for each object.

  8. After the loop, we draw the WebGL circles.

                // Parse the JSON from each elements
                let gameDataFrame = frameSource.streams["GameData"];
                let gameData: IGameData = null;
    
                // Log the annotations
                const colorChangedAnnotation = frameSource.annotations["ColorChanged"];
                if (colorChangedAnnotation) {
                    for (let annotation of colorChangedAnnotation) {
                        const dataColor: IGameChangeColorAnnotationList  = annotation.user;
                        for (let colorChangeElement of dataColor.colorList) {
                            console.info(`The cube ${colorChangeElement.name} changed to color ${colorChangeElement.color}`);
                        }
                    }
                }
    
                if (gameDataFrame && gameDataFrame.user) {
    
                    gameData = gameDataFrame.user;
                    this.lastGameData = gameData;
                    let cubes = gameData.cubes;
    
                    // Let update the time of camera and scene
                    let textForTime = "Time until next camera: " + gameData.camera.toFixed(2) + " seconds \r\n Time until next scene: " + gameData.scene.toFixed(2) + " seconds";
                    this.timeCamSceneDiv.textContent = textForTime;
    
                    // Setup the player table once the game data is ready
                    if (this.playerTableSetupCompletion === false) {
                        this.playerTableSetupCompletion = true;
                        let cubes = gameData.cubes;
    
                        this.initPlayerTable(cubes);
                    }
    
                    // Perform the webgl update process -- Update 3d
                    if (cubes) {
                        let vertices: number[] = [];
                        for (let cube of cubes) {
                            let m = cube.mat;
                            let p = genvidMath.vec3(m.e03, m.e13, m.e23);
                            let r = this.circleRadius;
                            let c = genvidMath.vec4(1, 0, 0, 1);
                            if (cube.color) {
                                c = genvidMath.vec4(cube.color.r, cube.color.g, cube.color.b, cube.color.a);
                            }
                            if (cube.selected && this.selection.length === 0) {
                                // To gather initial state.
                                this.setSelection(cube.name);
                            }
                            if (!this.isSelected(cube.name)) {
                                c = genvidMath.muls4D(c, 0.5);
                            }
                            this.makeCircleZ(vertices, p.x, p.y, p.z, r, c);
    
                            // Move the name tag of the cube
                            let tag = this.findOrCreateTagDiv(cube);
                            let mat = this.convertMatrix(gameData.MatProjView);
                            let pos_2d = genvidMath.projectPosition(mat, p);
                            this.center_at(tag, pos_2d, genvidMath.vec2(0, -75));
    
                            // Modify the popularity with the latest value
                            if (this.latestPopularity) {
                                let pCube = this.latestPopularity.cubes.find((c) => { return c.name === cube.name; });
                                if (pCube) {
                                    cube.popularity = pCube.popularity;
                                }
                            }
    
                            cube.popText = this.popularityToText(cube.popularity);
                            let cubePopSpan = <HTMLSpanElement>document.querySelector(".cube" + cube.name + "_cheer");
                            cubePopSpan.textContent = cube.popText;
    
                            let cubePosXSpan = <HTMLSpanElement>document.querySelector("#cube" + cube.name + "_position_x");
                            cubePosXSpan.textContent = cube.mat.e03.toFixed(2);
    
                            let cubePosYSpan = <HTMLSpanElement>document.querySelector("#cube" + cube.name + "_position_y");
                            cubePosYSpan.textContent = cube.mat.e13.toFixed(2);
    
                            let cubePosZSpan = <HTMLSpanElement>document.querySelector("#cube" + cube.name + "_position_z");
                            cubePosZSpan.textContent = cube.mat.e23.toFixed(2);
                        }
    
                        let num_quads = vertices.length / (4 * 9);
    
                        let genvidWebGL = this.genvidWebGL;
                        let cmd: RenderCommand = this.gfx_cmd_cubes;
                        cmd.vtx = genvidWebGL.createBuffer(new Float32Array(vertices));
                        cmd.idx = genvidWebGL.createIndexBufferForQuads(num_quads);
                    }
    
                    let mat = gameData.MatProjView;
                    this.gfx_prog_data_viewproj = [mat.e00, mat.e10, mat.e20, mat.e30, mat.e01, mat.e11, mat.e21, mat.e31, mat.e02, mat.e12, mat.e22, mat.e32, mat.e03, mat.e13, mat.e23, mat.e33];
    
                    this.gfx_draw3D();
                }
    
  9. Afterwards, we verify that the video is ready to perform other operations.

  10. If the video is ready, we update the Genvid overlay values.

  11. We update the prompt-overlay visibility (used to display user changes to the sound).

  12. Finally, we check if the video is fullscreen and update the status for it.

                if (this.videoReady) {
                    // Update the Genvid information overlay
                    let w = 18; // Width of the content of every line (without label).
                    let localTime: Date = new Date();
                    this.timeLocalDiv.textContent = `Local: ${this.msToDuration(Math.round(localTime.getTime()))}`;
    
                    let videoTimeRawMS = 0;
                    let videoPlayer = this.client.videoPlayer;
                    if (videoPlayer) {
                        videoTimeRawMS = videoPlayer.getCurrentTime() * 1000;
                    }
                    this.timeVideoRawDiv.textContent = `Raw Video: ${this.preN(this.msToDuration(Math.round(videoTimeRawMS)), w)}`;
    
                    this.timeVideoDiv.textContent = `Est. Video: ${this.preN(this.msToDuration(Math.round(this.client.videoTimeMS)), w)}`;
    
                    this.timeComposeLastDiv.textContent = `Last Compose: ${this.preN(this.msToDuration(Math.round(this.client.lastComposeTimeMS)), w)}`;
    
                    this.timeComposeDiv.textContent = `Est. Compose: ${this.preN(this.msToDuration(Math.round(this.client.composeTimeMS)), w)}`;
    
                    this.timeStreamDiv.textContent = `Stream: ${this.preN(this.msToDuration(Math.round(this.client.streamTimeMS)), w)}`;
    
                    this.latencyDiv.textContent = `Latency: ${this.preN(this.client.streamLatencyMS.toFixed(0), w - 3)} ms`;
    
                    this.delayOffsetDiv.textContent = `DelayOffset: ${this.preN(this.client.delayOffset.toFixed(0), w - 3)} ms`;
    
                    // Update the visibility on the overlay when using key press
                    if (this.promptOverlay.style.visibility === "visible" && this.timeVisiblePrompt < this.timeVisibleMax) {
                        this.timeVisiblePrompt++;
                        if (this.volumeChange === 2) {
                            this.volumeChange = 0;
                            this.promptOverlay.textContent = "Volume: " + this.client.videoPlayer.getVolume().toString() + " %";
                        } else if (this.volumeChange === 1) {
                            this.volumeChange = 0;
                            this.promptOverlay.textContent = "Volume: " + this.client.videoPlayer.getVolume().toString() + " %";
                        }
                    } else if (this.promptOverlay.style.visibility === "visible" && this.timeVisiblePrompt >= this.timeVisibleMax) {
                        this.promptOverlay.style.visibility = "hidden";
                    }
                }
                let isFullScreen = this.checkFullScreen();
                if (isFullScreen !== this.isFullScreen) {
                    this.isFullScreen = isFullScreen;
                }
    

initPlayerTable

initPlayerTable(cubeData: IGameDataCube[])

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 add HTML code for the table to a string 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(cubeData: IGameDataCube[]) {
                for (let cube of cubeData) {
                    let cubeAdd = `<div class='col-md-6 col-lg-4 nopadding'>
                                            <div class='cube clickable cube` + cube.name + `'>
                                                <div>
                                                    <span class='cube_name clickable'>` + cube.name + `</span>
                                                    <button class='cheer' id='` + cube.name + `_cheerbutton'><i class='icon_like' aria-hidden='true'></i></button>
                                                    <span class='cheer_value cube` + cube.name + `_cheer'></span>
                                                </div>
                                                <div>
                                                    <span class='label clickable cube` + cube.name + `_reset'>Reset</span>
                                                    <span id='cube` + cube.name + `_position_x' class='cube_position'></span>
                                                    <span id='cube` + cube.name + `_position_y' class='cube_position'></span>
                                                    <span id='cube` + cube.name + `_position_z' class='cube_position'></span>
                                                </div>
                                                <table class='cube_color text-center'>
                                                    <tr>
                                                        <td class='clickable color` + cube.name + `_green'>Green</td>
                                                        <td class='clickable color` + cube.name + `_white'>White</td>
                                                        <td class='clickable color` + cube.name + `_yellow'>Yellow</td>
                                                    </tr>
                                                    <tr>
                                                        <td class='clickable color` + cube.name + `_darkblue'>Dark blue</td>
                                                        <td class='clickable color` + cube.name + `_grey'>Grey</td>
                                                        <td class='clickable color` + cube.name + `_lightblue''>Light Blue</td>
                                                    </tr>
                                                    <tr>
                                                        <td class='clickable color` + cube.name + `_orange'>Orange</td>
                                                        <td class='clickable color` + cube.name + `_blue'>Blue</td>
                                                        <td class='clickable color` + cube.name + `_purple'>Purple</td>
                                                    </tr>
                                                </table>
                                            </div>
                                        </div>`;
    
                    $(".gameControlsDiv").append(cubeAdd);
    
                    let cheerButton = <HTMLButtonElement>document.querySelector("#" + cube.name + "_cheerbutton");
                    cheerButton.addEventListener("click", (_event) => { this.onCheer(cube.name); }, false);
    
                    let cubeDiv = <HTMLDivElement>document.querySelector(".cube" + cube.name);
                    cubeDiv.addEventListener("click", (_event) => { this.onSelect(cube.name, true); }, false);
                    this.cubeDiv.push(cubeDiv);
    
                    let resetButton = <HTMLSpanElement>document.querySelector(".cube" + cube.name + "_reset");
                    resetButton.addEventListener("click", (_event) => { this.onReset(cube.name); }, false);
    
                    for (let colorSelect of this.tableColor) {
                        let colorButton = <HTMLSpanElement>document.querySelector(".color" + cube.name + "_" + colorSelect[0]);
                        colorButton.addEventListener("click", (_event) => { this.onColorChange(cube.name, colorSelect[1]); }, false);
                    }
                }
            }
    

on_streams_received

on_streams_received(dataStreams: genvid.IDataStreams)

The on_streams_received method uses loops to get the latest timecode associated with the game data. We also convert the JSON data received into IGameData. We call it when the website receives the stream.

        private streamIdToFormat = {
            "GameData": "JSON",
            "Popularity": "JSON",
            "ColorChanged": "JSON",
            "GameCopyright": "UTF8"
        };
        // Upon receving the stream, get the timecode and the data
        private on_streams_received(dataStreams: genvid.IDataStreams) {
            for (let stream of [...dataStreams.streams, ...dataStreams.annotations]) {
                // Using switch...case because different operations can be made depending on the stream ID.
                switch (this.streamIdToFormat[stream.id]) {
                    case "JSON": {
                        // Parse the JSON from each elements
                        for (let frame of stream.frames) {
                            if (this.last_game_time_received < frame.timeCode) {
                                this.last_game_time_received = frame.timeCode;
                            }
                            let jsonOutput = this.tryParseJSON(frame.data);
                            if (jsonOutput) {
                                frame.user = jsonOutput;
                            }
                        }
                        break;
                    }
                    default:
                        break;
                }
            }
        }
        // Returns JSON parsed string or null if it couldn't be parsed.
        private tryParseJSON(str, streamId = null, streamSessionId = null): any {
            try {
                let retVal = JSON.parse(str);
                if (retVal && typeof retVal === "object") {
                    return retVal;
                }
            }
            catch (e) {
                if (streamId || streamSessionId) {
                    console.info(`invalid Json format on '${streamId}:${streamSessionId}' for '${str}' with error '${e}'`);
                } else {
                    console.info(`invalid Json format for '${str}' with error '${e}'`);
                }
            }
            return null;
        }

on_notifications_received

on_notifications_received(message: genvid.IDataNotifications)

The on_notifications_received method checks if any notification’s ID is Popularity. When it is, we convert the data into a string and transform the JSON data into ICubePopularity. We call this method when the website receives a notification.

        // Upon receiving a notification, get the notification content
        private on_notifications_received(message: genvid.IDataNotifications) {
            for (let notification of message.notifications) {
                if (notification.id === "Popularity") {
                    let datastr = genvid.UTF8ToString(notification.rawdata);
                    try {
                        // Get the latest popularity
                        let userData = <ICubePopularities>JSON.parse(datastr);
                        this.latestPopularity = userData;
                    }
                    catch (err) {
                        console.info("invalid Json format for:" + datastr + " with error :" + err);
                    }

                }
            }
        }