Admin.ts¶
The admin page can be accessed by clicking on Admin on the index.html. After entering the username and password (admin for both), the User can interact with the game content by interacting with the player tables. In the document adminUnity.ts, we have all the code used to perform these interactions.
In the file, we have one interface and one class.
In This Section
interface ICommandRequest¶
The ICommandRequest interface sends a command to the game.
interface ICommandRequest {
id: string;
value: string;
}
class AdminController¶
The AdminController class contains all the methods needed to connect to the stream, display the proper information, and send the commands to the game.
Outside of the class, we create an instance of AdminController and perform an onConnect() on it to start the connection to the stream.
let admin = new unitySample.AdminController("video_player_hidden");
admin.onConnect();
We will cover the content of the AdminController class in the following sections.
onConnect¶
onConnect()
The onConnect method starts the connection to the services. If the
connection executes properly, we proceed to the on_channel_join
method with the IChannelJoinResponse()
information
found. We do this process in exactly the same way in the unity.ts
file.
// Start the connection to the services
onConnect() {
let promise = $.post("/api/public/channels/join", {}, (joinRep) => {
this.on_channel_join(<genvid.IChannelJoinResponse>joinRep);
});
promise.fail((err) => {
alert("Can't get the stream info:" + err);
});
}
on_channel_join¶
on_channel_join(info: genvid.IChannelJoinResponse)
The on_channel_join method creates the Genvid client after connecting to the
services and channel. We use the information found during this process
and the video_player_id sent during the class creation process to
create the client (it is an argument to the AdminController
class). We do this process in a similar way in the unity.ts
file,
although we do not need some methods in this case.
Afterwards, we need to associate specific events to functions in this class:
- onStreamsReceived
- Triggered when the stream content is received (used to get the timecode and
- game data).
- onDraw
- Triggered when drawing a new frame in the video.
We then proceed to start the client with the start()
method.
// Create the genvid Client and the function listening to it
private on_channel_join(joinRep: genvid.IChannelJoinResponse) {
this.streamInfo = joinRep.info;
this.client = genvid.createGenvidClient(this.streamInfo, joinRep.uri, joinRep.token, this.video_player_id);
this.client.onStreamsReceived((streams) => { this.on_streams_received(streams); });
this.client.onDraw((frame) => { this.on_new_frame(frame); });
this.client.start();
}
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. We do this
process in exactly the same way in the unity.ts
file.
// Upon receving the stream
private on_streams_received(dataStreams: genvid.IDataStreams) {
for (let stream of dataStreams.streams) {
for (let frame of stream.frames) {
if (this.last_game_time_received < frame.timeCode) {
this.last_game_time_received = frame.timeCode;
}
}
}
// Parse the JSON from each elements
for (let stream of [...dataStreams.streams, ...dataStreams.annotations]) {
for (let frame of stream.frames) {
frame.user = JSON.parse(frame.data);
}
}
}
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 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 and that the player table is not created, we proceed to get the object list from the data (cubes).
We create a loop for each object and we create the player table for each.
We add the event listeners for clicking on the buttons of the player table.
After the loop we create two buttons for the scene and camera change.
We add event listeners for clicking the scene and camera change buttons.
// During a new frame, if the game data is valid, the player table is created
private on_new_frame(frameSource: genvid.IDataFrame) {
// Parse the JSON from each elements
let gameDataFrame = frameSource.streams["GameData"];
let gameData = null;
if (gameDataFrame && gameDataFrame.user) {
if (this.message === "Unable to retreive data from the stream, is it still active ?") {
this.message = "Success: data received from the stream";
this.displayMessage();
}
if (this.playerTableSetup === false) {
this.playerTableSetup = true;
gameData = gameDataFrame.user;
let cubes = gameData.cubes;
for (let cube of cubes) {
let cubeAdd = "<table>" +
"<tr>" +
"<td id='table_name'>" + cube.name + "</td>" +
"</tr>" +
"<tr>" +
"<td>" +
"<div id='command_button' class='" + cube.name + "_upDirection'>↑</div>" +
"<div id='command_button' class='" + cube.name + "_downDirection'>↓</div>" +
"<div id='command_button' class='" + cube.name + "_leftDirection'>←</div>" +
"<div id='command_button' class='" + cube.name + "_rightDirection'>→</div>" +
"</td>" +
"</tr>" +
"<tr>" +
"<td id='command_button'><div class='" + cube.name + "_reset'>Reset</div></td>" +
"</tr>" +
"<tr>" +
"<td id='command_button'><div class='" + cube.name + "_slower'>Slower</div></td>" +
"</tr>" +
"<tr>" +
"<td id='command_button'><div class='" + cube.name + "_faster'>Faster</div></td>" +
"</tr>" +
"</table>";
$(".admin_table_section").append(cubeAdd);
let upButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_upDirection");
upButton.addEventListener("click", (_event) => { this.setDirection(cube.name, 0, 1); }, false);
let downButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_downDirection");
downButton.addEventListener("click", (_event) => { this.setDirection(cube.name, 0, -1); }, false);
let leftButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_leftDirection");
leftButton.addEventListener("click", (_event) => { this.setDirection(cube.name, -1, 0); }, false);
let rightButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_rightDirection");
rightButton.addEventListener("click", (_event) => { this.setDirection(cube.name, 1, 0); }, false);
let resetButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_reset");
resetButton.addEventListener("click", (_event) => { this.reset(cube.name); }, false);
let slowerButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_slower");
slowerButton.addEventListener("click", (_event) => { this.changeSpeed(cube.name, 0.8); }, false);
let fasterButton = <HTMLButtonElement>document.querySelector("." + cube.name + "_faster");
fasterButton.addEventListener("click", (_event) => { this.changeSpeed(cube.name, 1.25); }, false);
}
let sceneCameraButtons = "<table>" +
"<tr>" +
"<td id='command_button' class='cameraChange'>Camera change</td>" +
"</tr>" +
"<tr>" +
"<td>" +
"<div id='command_button' class='sceneChange'>Scene change</div>" +
"</td>" +
"</tr>" +
"</table>";
$(".admin_table_section").append(sceneCameraButtons);
let cameraButton = <HTMLButtonElement>document.querySelector("." + "cameraChange");
cameraButton.addEventListener("click", (_event) => { this.changeCamera(); }, false);
let sceneButton = <HTMLButtonElement>document.querySelector("." + "sceneChange");
sceneButton.addEventListener("click", (_event) => { this.changeScene(); }, false);
}
}
else {
this.message = "Unable to retreive data from the stream, is it still active ?";
this.displayErrorMessage();
}
}
setDirection¶
setDirection(cubeName: string, x: number, z: number)
The setDirection sends a command to the game to set the direction for a player. We call it when clicking a direction for a player.
// Change the direction of the cube
setDirection(cubeName: string, x: number, z: number) {
this.message = this.error = "";
let command: ICommandRequest = {
id: "direction",
value: `${cubeName}:${x}:0:${z}`
};
let promise = $.post("/api/admin/commands/game", command).then(() => {
this.message = `Set direction to ${cubeName}:${x}:${z}`;
this.displayMessage();
});
promise.fail((err) => {
this.message = `Failed with error ${err} to do Set direction to ${cubeName}:${x}:${z}`;
this.displayErrorMessage();
});
}
changeSpeed¶
changeSpeed(cubeName: string, factor: number)
The changeSpeed method sends a command to the game to indicate that an object needs to go faster or slower. We call it when clicking on Slower or Faster for a player.
// Change the speed of the cube
changeSpeed(cubeName: string, factor: number) {
this.message = this.error = "";
let command: ICommandRequest = {
id: "speed",
value: `${cubeName}:${factor}`
};
let promise = $.post("/api/admin/commands/game", command).then(() => {
this.message = `changeSpeed ${cubeName}:${factor}`;
this.displayMessage();
});
promise.fail((err) => {
this.message = `Failed with error ${err} to do changeSpeed ${cubeName}:${factor}`;
this.displayErrorMessage();
});
}
reset¶
reset(cubeName: string)
The reset method sends a command to the game to set an object to its original position. We call it when clicking on Reset for a player.
// Reset the position of the cube
reset(cubeName: string) {
this.message = this.error = "";
let command: ICommandRequest = {
id: "reset",
value: cubeName
};
let promise = $.post("/api/admin/commands/game", command).then(() => {
this.message = `reset ${cubeName}`;
this.displayMessage();
});
promise.fail((err) => {
this.message = `Failed with error ${err} to do reset ${cubeName}`;
this.displayErrorMessage();
});
}
changeCamera¶
changeCamera()
The changeCamera method sends a command to the game to change the current camera. We call it when clicking on the Camera change button.
changeCamera() {
this.message = this.error = "";
let command: ICommandRequest = {
id: "camera",
value: "change"
};
let promise = $.post("/api/admin/commands/game", command).then(() => {
this.message = `camera change done`;
this.displayMessage();
});
promise.fail((err) => {
this.message = `Failed with error ${err} to change the camera`;
this.displayErrorMessage();
});
}
changeScene¶
changeScene()
The changeScene method sends a command to the game to change the current scene. We call it when clicking on the Scene change button.
changeScene() {
this.message = this.error = "";
let command: ICommandRequest = {
id: "scene",
value: "change"
};
let promise = $.post("/api/admin/commands/game", command).then(() => {
this.message = `scene change done`;
this.displayMessage();
});
promise.fail((err) => {
this.message = `Failed with error ${err} to change the scene`;
this.displayErrorMessage();
});
}
displayMessage¶
displayMessage()
The displayMessage method displays a success message about a completed operation. It’s useful for knowing if we get the data from the stream properly and to indicate if a command operation is successful.
// Display a message in the page as a sucess
displayMessage() {
let messageErrorDiv = <HTMLDivElement>document.querySelector("#alert_error_cube");
messageErrorDiv.style.visibility = "hidden";
let messageDiv = <HTMLDivElement>document.querySelector("#alert_sucess_cube");
messageDiv.style.visibility = "visible";
let messageSpan = <HTMLSpanElement>document.querySelector("#sucess_message_cube");
messageSpan.textContent = this.message;
}
displayErrorMessage¶
displayErrorMessage()
The displayErrorMessage method displays an error message about a completed operation. It’s useful for knowing if we didn’t get the data from the stream properly and to indicate if a command operation isn’t successful.
// Display a message in the page as an error
displayErrorMessage() {
let messageDiv = <HTMLDivElement>document.querySelector("#alert_sucess_cube");
messageDiv.style.visibility = "hidden";
let messageErrorDiv = <HTMLDivElement>document.querySelector("#alert_error_cube");
messageErrorDiv.style.visibility = "visible";
let messageSpan = <HTMLSpanElement>document.querySelector("#error_message_cube");
messageSpan.textContent = this.message;
}