unityController - 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.
In This Section
onKeyDown¶
onKeyDown(event: KeyboardEvent)
The onKeyDown method triggers a specific function depending on which key is pressed while the window is selected.
- changeOffset
- Changes the video delay offset by changing the value.
- toggleGenvidOverlay
- Displays or hides the Genvid overlay.
- client.videoPlayer.isPaused
- Returns a boolean indicating if the video is paused.
- client.videoPlayer.play
- Plays the video.
- client.videoPlayer.pause
- Pauses the video.
- client.videoPlayer.getMuted
- Returns a boolean indicating if the video sound is muted.
- client.videoPlayer.setMuted
- Sets the sound to mute or unmute depending of the argument.
- client.videoPlayer.setVolume
- Sets the video volume to the sent argument.
- client.videoPlayer.getVolume
- Gets the current video volume.
- onHelpActivation
- Displays or hides the help overlay.
onKeyDown(event: KeyboardEvent) {
// 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);
if (code === "Equal" || code === "NumpadAdd") {
this.changeOffset(+1, event);
} else if (code === "Minus" || code === "NumpadSubtract") {
this.changeOffset(-1, event);
} else if (code === "NumpadMultiply") {
this.changeOffset(0, event);
} else if (code === "KeyG") {
this.toggleGenvidOverlay();
}
else if (code === "Space") {
if (this.client.videoPlayer.isPaused()) {
this.client.videoPlayer.play();
} else {
this.client.videoPlayer.pause();
}
event.preventDefault();
}
else if (code === "KeyM") {
this.onMute();
}
else if (code === "KeyZ") {
this.promptOverlay = <HTMLDivElement>document.querySelector("#prompt_overlay");
this.client.videoPlayer.setVolume(this.client.videoPlayer.getVolume() - 20);
this.promptOverlay.style.visibility = "visible";
this.timeVisiblePrompt = 0;
this.volumeChange = 2;
}
else if (code === "KeyX") {
this.promptOverlay = <HTMLDivElement>document.querySelector("#prompt_overlay");
this.client.videoPlayer.setVolume(this.client.videoPlayer.getVolume() + 20);
this.promptOverlay.style.visibility = "visible";
this.timeVisiblePrompt = 0;
this.volumeChange = 1;
}
else if (code === "KeyH") {
this.onHelpActivation();
}
}
getKeyCode¶
getKeyCode(event: KeyboardEvent)
The getKeyCode method gets a proper keyCode on different browsers.
// Compatibility code for browsers (Safari) not having KeyboardEvent.code.
getKeyCode(event: KeyboardEvent) {
if (event.keyCode) {
console.log(event.keyCode, event.code);
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,
- promptOverlay (used of sound modification), and
- 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.
// Allow to adjust the various overlay when resizing the windows - needed to see the PromptOverlay and Name moving div
onResize() {
let refElement = this.client.videoElem; // The element to match.
let refElementSize = refElement ? genvidMath.vec2(refElement.clientWidth, refElement.clientHeight) : genvidMath.vec2(1280, 720);
let refElementRatio = refElementSize.x / refElementSize.y;
let videoRatio = this.client.videoAspectRatio;
let pos: genvidMath.IVec2;
let size: genvidMath.IVec2;
if (videoRatio >= refElementRatio) {
// Top+Bottom bars, fill width fully, shrink height.
let ey = refElementSize.x / videoRatio;
let dy = refElementSize.y - ey;
// Center vertically.
let y = dy * 0.5;
pos = genvidMath.vec2(0, Math.round(y));
size = genvidMath.vec2(refElementSize.x, Math.round(ey));
} else {
// Left+Right bars, fill height fully, shrink width.
let ex = refElementSize.y * videoRatio;
let dx = refElementSize.x - ex;
// Center horizontally.
let x = dx * 0.5;
pos = genvidMath.vec2(Math.round(x), 0);
size = genvidMath.vec2(Math.round(ex), refElementSize.y);
}
let style = this.videoOverlay.style;
let cur_pos = genvidMath.vec2(parseInt(style.left), parseInt(style.top));
let cur_size = genvidMath.vec2(parseInt(style.width), parseInt(style.height));
if (!genvidMath.equal2D(cur_size, size, 0.9) || !genvidMath.equal2D(cur_pos, 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); // Render in the whole area.
}
if (this.lastSelection != null) {
this.onSelect(this.lastSelection, true);
}
else {
for (let nameSelect of this.cubeDiv) {
nameSelect.style.backgroundColor = "#181818";
}
}
}
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() {
let doc = <any>document;
if (this.checkFullScreen()) {
if (doc.exitFullscreen) {
doc.exitFullscreen();
} else if (doc.mozCancelFullScreen) {
doc.mozCancelFullScreen();
} else if (doc.webkitExitFullscreen) {
doc.webkitExitFullscreen();
} else if (doc.msExitFullscreen) {
doc.msExitFullscreen();
}
this.fullScreenElement.classList.remove("fa-compress");
this.fullScreenElement.classList.add("fa-expand");
} else {
let element = <any>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.fullScreenElement.classList.remove("fa-expand");
this.fullScreenElement.classList.add("fa-compress");
}
}
onMute¶
onMute()
The onMute 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, mute or unmute the video player audio
onMute() {
const muteIcon = document.querySelector("#mute-button i");
this.promptOverlay = <HTMLDivElement>document.querySelector("#prompt_overlay");
if (this.client.videoPlayer.getMuted()) {
muteIcon.classList.remove("fa-volume-off");
muteIcon.classList.add("fa-volume-up");
this.client.videoPlayer.setMuted(false);
this.promptOverlay.style.visibility = "visible";
this.promptOverlay.textContent = "Volume is unmuted";
this.timeVisiblePrompt = 0;
} else {
muteIcon.classList.remove("fa-volume-up");
muteIcon.classList.add("fa-volume-off");
this.client.videoPlayer.setMuted(true);
this.promptOverlay.style.visibility = "visible";
this.promptOverlay.textContent = "Volume is muted";
this.timeVisiblePrompt = 0;
}
}
showOverlay¶
showOverlay()
The showOverlay method displays the Genvid overlay.
// Change the style to display the Genvid overlay
showOverlay() {
this.genvidOverlay.style.display = "block";
}
hideOverlay¶
hideOverlay()
The hideOverlay method hides the Genvid overlay.
// Change the style to hide the Genvid overlay
hideOverlay() {
this.genvidOverlay.style.display = "none";
}
clickCube¶
clickCube(event: MouseEvent)
The clickCube function returns true if the pickCube method determines that a cube was selected by clicking the WebGL overlay. If not, we handle the event propagation in this function.
// Method used when clicking on the WebGL overlay
clickCube(event: MouseEvent) {
let best = this.pickCube(event);
if (best) {
return true;
} else {
// Continue propagation.
return false;
}
}
pickCube¶
pickCube(event: MouseEvent)
The pickCube method determines if a cube is selected by clicking on the WebGL overlay.
We verify that the game data is valid, otherwise we are not doing any verification.
We get the position of the click in the window.
Then we convert this coordinate into the projected space.
if (this.lastGameData == null) { return false; } // [0, 1] coordinates in the window. let rect = this.canvas3d.getBoundingClientRect(); let x = event.pageX - rect.left; // More robust to recompute from pageX/pageY. let y = event.pageY - rect.top; let p01 = genvidMath.vec2(x / rect.width, y / rect.height); // [-1, 1] coordinates in projection space. let p = genvidMath.mad2D(p01, genvidMath.vec2(2, -2), genvidMath.vec2(-1, 1)); let mat = this.convertMatrix(this.lastGameData.MatProjView); let best = null; let bestDist = Infinity;
We start a loop to verify the coordinates of each object.
In this loop, we get the object coordinates and apply a radius to have a zone to search.
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.e03, cube.mat.e13, cube.mat.e23); let radius = 1.0; let pos2D_rad2D = this.projectWithRadius(mat, pos3D, radius); let pos2D = pos2D_rad2D[0]; let rad2D = pos2D_rad2D[1]; let pos2D_to_p = genvidMath.sub2D(p, pos2D); let d = genvidMath.length2D(pos2D_to_p); if (d < rad2D && d < bestDist) { best = cube.name; bestDist = d; } } if (event.ctrlKey || event.metaKey) { this.toggleSelection(best); this.onSelect(best, false); } else if (best != null) { this.setSelection(best); this.onSelect(best, false); } return best;
toggleSelection¶
toggleSelection(name)
The toggleSelection method adds the name of the object sent as a selection.
// Select a cube via the circle
toggleSelection(name) {
if (!this.removeFromArray(this.selection, name)) {
this.addToSelection(name);
}
}
setSelection¶
setSelection(name)
The setSelection method resets the selection list to only the selected object.
// Set the selection to a specific cube
setSelection(name) {
this.selection = [];
if (name) {
this.selection.push(name);
this.client.sendEventObject({ select: name });
}
}
isSelected¶
isSelected(name)
The isSelected method tells us if an object is selected or not.
// Verify if the cube is selected
isSelected(name) {
return this.selection.indexOf(name) !== -1;
}
addToSelection¶
addToSelection(name)
The addToSelection method adds an object as a selection if the object was not previously selected.
// Add the cube selected to the selection list
addToSelection(name) {
if (name && !this.isSelected(name)) {
this.selection.push(name);
}
}
changeOffset¶
changeOffset(direction, event)
The changeOffset 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 changed the delay offset depending of the key pressed
private changeOffset(direction, event) {
if (direction !== 0) {
let delayDelta = 100 * direction;
if (event.altKey) delayDelta *= 10;
if (event.shiftKey) delayDelta /= 2;
let newDelayOffset = this.client.delayOffset + delayDelta;
this.client.delayOffset = newDelayOffset;
} else {
this.client.delayOffset = 0;
}
}
toggleGenvidOverlay¶
toggleGenvidOverlay()
The toggleGenvidOverlay method displays or hides the Genvid overlay. The Genvid overlay displays various stats about the stream.
- Raw Video
- Time when the raw video is received.
- Est. Video
- Estimated time when the video is received.
- Last Compose
- Last compose time.
- Est. Compose
- Estimated compose time.
- Stream
- Time since the streaming has started.
- Latency
- Latency occurring when watching the stream.
- DelayOffset
- Current delay offset for the video.
// Display or remove the Genvid Overlay
private 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.
// Display or remove the help overlay
private onHelpActivation() {
if (this.help_overlay.style.visibility === "visible") {
this.help_overlay.style.visibility = "hidden";
}
else {
this.help_overlay.style.visibility = "visible";
}
}
onCheer¶
onCheer(cubeName: string)
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
private onCheer(cubeName: string) {
this.client.sendEventObject({ cheer: cubeName });
}
onReset¶
onReset(cubeName: string)
The onReset method sends an event when a button is clicked to reset a player. The event contains the name of the object.
// Reset the position of the cube
private onReset(cubeName: string) {
this.client.sendEventObject({ reset: cubeName });
}
onColorChange¶
onColorChange(cube: string, color: string)
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
private onColorChange(cube: string, color: string) {
let evt = {
key: ["changeColor", cube],
value: color,
};
this.client.sendEvent([evt]);
}
onSelect¶
onSelect(cubeName: string, selectionInterface: boolean)
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.
// Select the cube from the interface
private onSelect(cubeName: string, selectionInterface: boolean) {
for (let nameSelect of this.cubeDiv) {
nameSelect.style.backgroundColor = "#181818";
}
let cubeDiv = <HTMLDivElement>document.querySelector(".cube" + cubeName);
this.lastSelection = cubeName;
cubeDiv.style.backgroundColor = "#32324e";
if (selectionInterface) {
this.setSelection(cubeName);
}
}