Cube Sample Website

For the cube 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.

  • /api/streams => /disco/stream/info

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

  • /api/channels/join => /disco/stream/join

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

  • /api/commands/game

This web method sends commands to the game. In this cube sample, you can access the method from the admin page. It is protected by a password.

The utils/auth.ts file is where we configure users and passwords.

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 picture-in-picture composition (see the Studio documentation). There is a video overlay showing circles around the cube. The position of the circle matches the position of the cubes in the video. The user can interact with the web page 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
  • Plus = Increase the DelayOffset
  • Minus = Decrease the DelayOffset
  • Multiply = 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:

  • Click 👍 = Change player popularity (heart icon)
  • Click reset = Reset cube position
  • Click color = Change cube color

The Genvid overlay is used to prevent interaction with the Youtube player, but it is also used to display the colored circles and names beside the cube. The overlay is also used to display useful debug information:

  • Local: Local time of the machine displaying the webpage
  • Raw Video: Current internal time of the video player
  • Est. Video: Estimates 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

You can access the admin page with the Admin button. The default login is admin and the password is admin. The page allows the admin to use the command channel. Currently, the page is divided into sections for each cube and contains these functions:

  • Direction - Click on any arrow to change the cube direction
  • Reset - Click to reset the position of the cube
  • Slower - Click to reduce the speed of the cube
  • Faster - Click to increase the speed of the cube

channelComponent.ts

The front-end code must instantiate a GenvidClient instance. This is done in the public/lib/channelComponent.ts file, after retrieving the stream info on /api/streams and making a join request on /api/channels/join.

The GenvidClient communicates with the Genvid services and provides a few important callbacks.

  • onVideoPlayerReady is called when the video is ready.
  • onAuthenticated informs about the success of the authentication.
  • onStreamsReceived is triggered when stream data is received.
  • onDraw is called periodically to refresh the GUI.
        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 in the following files:

  • public/lib/components/cubeControls.html
  • public/lib/components/cubeCubeControl.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 are controlling the game, the admin page needs authentication. The default users are:

admin admin
user1 user1
user2 user2