unityController - Enter Frame¶
The enter frame section contains mostly methods that are triggered each frame or in a similar way.
In This Section
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.
We get the annotation data first, verify that it’s valid, and display a console log afterwards.
We need to get the game data and verify that this data is valid before doing any operation with it.
Once we are certain about the data validity, we get the object list from the data (cubes).
We update the text field for the time left until the next camera and scene.
We verify if we set up the player table. If that isn’t the case, we call
initPlayerTable
.We use the objects array to create a loop that creates the circles made in WebGL.
With the same loop, we update the popularity value and the position value for each object.
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(); }
Afterwards, we verify that the video is ready to perform other operations.
If the video is ready, we update the Genvid overlay values.
We update the prompt-overlay visibility (used to display user changes to the sound).
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.
We start with a loop that repeats for each object’s information, based on received game data.
We add HTML code for the table to a string that we later append to an HTML tag.
We then add the event listeners for the table, cheer, and reset click functionality.
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);
}
}
}
}