Cube Sample Website

For the Cube Sample website, we use Node.js for the server and rely on AngularJS 1.5 for the front-end. We also use TypeScript throughout our code in order to benefit from type checking.

This section only covers the parts of the code that link each component with the Genvid Web API.

Server Code

The server is written using Node.js and is responsible for connecting some public URLs to the internal Genvid Services.

The routes/streams.t.ts file is where we specify the rerouting. This file forwards web requests from the client to the disco service using a pre-determined secret.

We discover the URL and secret of the disco and commands services using Consul. The config.ts file shows how to discover those services.

// Copyright 2016-2017 Genvid Technologies LLC All Rights Reserved.
let envs = require("envs");
import * as Consul from "consul";

let consul_addr = envs("CONSUL_HTTP_ADDR", "127.0.0.1:8500").split(":");
let port = consul_addr.pop()
let host = consul_addr.join(":")
let consul = Consul({host: host, port: port});

export let disco_url = envs("GENVID_DISCO_URL", "http://localhost:8080");
export let disco_secret = envs("GENVID_DISCO_SECRET", "secretcode");

export let command_url = envs("GENVID_COMMAND_URL", "http://localhost:8089");
export let command_secret = envs("GENVID_COMMAND_SECRET", "secretcodecommand");

export interface ITaggedAddresses {
    lan: string;
    wan: string;
}

export interface IServiceNode {
    Node: string;
    Address: string;
    TaggedAddresses: ITaggedAddresses;
    ServiceID: string;
    ServiceName: string;
    ServiceTags: any[];
    ServiceAddress: string;
    ServicePort: number;
    ServiceEnableTagOverride: boolean;
    CreateIndex: number;
    ModifyIndex: number;
}

function watchService(serviceName: string, setUrl: (url: string) => void) {
    let nodesOptions: Consul.Catalog.Service.NodesOptions = {
        service: serviceName,        
    };

    let serviceWatch = consul.watch({
        method: consul.catalog.service.nodes,
        options: nodesOptions,
    });

    serviceWatch.on("change", (nodes: IServiceNode[], _res) => {
        if (nodes.length === 0) {
            console.error(`${serviceName} service is not available from consul`);
        } else {
            let node = nodes[0];
            // Use the first disco service
            let serviceUrl = `http://${node.ServiceAddress}:${node.ServicePort}`;
            setUrl(serviceUrl);
            console.info(`Watch ${serviceName} url: ${serviceUrl}`);
        }
    });

    serviceWatch.on("error", (err) => {
        console.error(`${serviceName} watch error:`, err);
    });
}

watchService("disco", (url) => { disco_url = url; });
watchService("command", (url) => { command_url = url; });

The server forwards 3 methods.

This method gets information about the current stream. This is how the browser is able to get the stream name, description, channel, etc.

This web method returns a URI and a token used to connect to the Genvid system.

This web method sends commands to the game. In this tutorial, you can access the method from the admin page which is password protected.

The utils/auth.ts file contains the user and password configuration.

Browser Code

The browser code relies on AngularJS 1.5. The result looks like this:

../../../_images/cube.png

It is a simple web application showing a live video of colored cubes. This application is also compatible with multistreams so you can try using picture-in-picture composition. (See the Genvid Studio sample for more information). There is a video overlay showing circles around the cubes. The positions of the circles match the position of the cubes in the video. The user can interact with the webpage directly with these controls:

Global:

  • m = Mute or unmute the stream.
  • z = Reduce volume of the stream.
  • x = Increase volume of the stream.
  • Space = Pause or unpause the stream.
  • + = Increase the DelayOffset.
  • - = Decrease the DelayOffset.
  • * = Reset the DelayOffset.
  • g = Show or hide the Genvid overlay.
  • h = Open or close the help menu.

Video:

  • Click on cube = Panel highlight and circle brighter

Header Buttons:

  • Play = Return to the interactive video player.
  • Admin = Access the admin page (u: admin p: admin).
  • Genvid icon button = Show or hide the Genvid overlay.
  • ? = Open or close the help menu.

Bottom panels:

  • 👍 = Change player popularity (heart icon).
  • Reset = Reset cube position.
  • Color = Change cube color.

The Genvid overlay prevents interaction with the YouTube player, but also displays the colored circles and names beside each cube along with useful debug information:

  • Local = Local time of the machine displaying the webpage.
  • Raw Video = Current internal time of the video player.
  • Est. Video = Estimated video player time.
  • Last Compose = Time of the last data reported by the Compose service.
  • Est. Compose = Estimated time of the Compose service.
  • Stream = Current time of the generalized stream.
  • Latency = Estimated latency between the game and the spectators.
  • DelayOffset = Optional delay offset used for minute synchronization
    adjustments.

The Admin page is where you access the command channel. Select the Admin button to go to the admin page and log in. (The default login is admin and the password is admin.) The page shows a section for each cube and includes the following functions:

  • Direction = Click on any arrow to change the cube direction.
  • Reset = Reset the position of the cube.
  • Slower = Reduce the speed of the cube.
  • Faster = Increase the speed of the cube.

channelComponent.ts

The front-end code must instantiate a IGenvidClient() instance. This is done in the public/lib/channelComponent.ts file, after it has retrieved the stream info on /api/streams and made a join request on /api/channels/join.

The IGenvidClient() communicates with the Genvid services and provides a few important callbacks.

        onDraw(frame: genvid.IDataFrame) {
            // Parse the JSON from each elements
            let gameDataFrame = frame.streams["GameData"];
            let gameData: IGameData = null;
            if (gameDataFrame && gameDataFrame.user) {
                gameData = gameDataFrame.user;

                // Modify the popularity with the latest value
                for (let cube of gameData.cubes) {
                    if (this.latestPopularity) {
                        let pCube = this.latestPopularity.Popularities.find((c) => { return c.name === cube.name; });
                        if (pCube) {
                            cube.popularity = pCube.popularity;
                        }
                    }
                }
            }

            // Send the data to the overlay and controls
            this.overlay.drawFrame(gameData);
            if (this.onChannelDraw) {
                this.onChannelDraw(gameData);
            }

            // Log the annotations
            let colorChangedAnnotation = frame.annotations["ColorChanged"];
            if (colorChangedAnnotation) {
                for (let annotation of colorChangedAnnotation) {
                    let colorChanges = <IGameChangeColorAnnotation[]>annotation.user;
                    for (let colorChange of colorChanges) {
                        console.info(`The cube ${colorChange.name} changed color.`);
                    }
                }
            }

            let isFullScreen = this.checkFullScreen();
            if (isFullScreen !== this.isFullScreen) {
                this.isFullScreen = isFullScreen;
                this.$scope.$digest();
            }
        }

cubeOverlay.ts

The file public/lib/cubeOverlay.ts shows how to read the game data and gives an example on how to use this information to render 3D circles in a WebGL canvas.

cubeControlsComponent.ts

This file shows how to generate events, such as changing a cube’s color, cheering for a cube, or resetting its position.

For example, the following code demonstrates how to change the color with an event.

        onColorChange(event: UIEvent, name: string, color: string) {
            event.stopPropagation();
            let evt = {
                key: ["changeColor", name],
                value: color,
            };
            this.parent.client.sendEvent([evt]);
        }

The HTML is contained in these files:

  • public/lib/components/tutorialControls.html
  • public/lib/components/tutorialCubeControl.html

adminComponent.ts

This file shows how to send commands to the game with the command channel. The admin user can change the speed and direction of the cubes.

Because the commands control the game, the admin page requires authentication. The default users are:

admin admin
user1 user1
user2 user2