unityController - Genvid Client Initialization

The Genvid Client initialization section contains most of the code needed to start the stream, initialize various variables, and assign several event listeners.

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.

        // 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 unityController class).

Afterwards, we need to associate specific events to function in this class:

onVideoPlayerReady
Triggers when the video stream is ready (used to initialize content).
onStreamsReceived
Triggers when the stream content is received (used to get the timecode and
game data).
onNotificationsReceived
Triggers when a notification is received (used for the popularity).
onDraw
Triggers when drawing a new frame in the video.

We then 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.onVideoPlayerReady((elem) => { this.on_video_player_ready(elem); });
            this.client.onStreamsReceived((streams) => { this.on_streams_received(streams); });
            this.client.onNotificationsReceived(this.on_notifications_received.bind(this));
            this.client.onDraw((frame) => { this.on_new_frame(frame); });
            this.client.onDisconnect(() => {this.on_disconnect_detected();});
            this.client.start();
        }

on_disconnect_detected

on_disconnect_detected()

In this sample, we use the onDisconnect() callback (exposed by the IGenvidClient() interface) to notify us when the client’s socket closes. We do so by binding our desired callback.

this.client.onDisconnect(() => {this.on_disconnect_detected();});

Now we instruct the script to launch reconnection attempts when we’re notified the client socket closed.

        private on_disconnect_detected(){
            let promise = $.post("/api/public/channels/join", {}, (joinRep) => {
                this.client.reconnect(joinRep.info, joinRep.uri, joinRep.token);
                this.resetFibNums();
            });
            promise.fail( async () => {
                await this.sleep(this.getNewSleepDuration());
                this.on_disconnect_detected();
            });
        }

Here we launch an information request asking for a new leaf address, a new token, and new streamInfo.

If the request succeeds, we call reconnect() to establish a new websocket connection using the above information.

If the request fails (for example, if there is no leaf available), we wait and retry. We determine the waiting period using the result of an incremental Fibonacci operation.

        private sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        private getNewSleepDuration() {
            this.fibNumResult = this.fibNumOne + this.fibNumTwo;
            this.fibNumOne = this.fibNumTwo;
            this.fibNumTwo = this.fibNumResult;
            this.delayBetweenReconnectionAttempts = this.fibNumResult + (this.fibNumResult * Math.floor(Math.random() * 10) * 0.01); // + 0 to 10% 
            return this.delayBetweenReconnectionAttempts * 1000; // milliseconds
        }

        private resetFibNums() {
            this.fibNumOne = 0;
            this.fibNumTwo = 1;
        }

Note that we add a ten-percent randomness factor to the result of the Fibonacci sequence. This avoids cases where two services launch connection attempts simultaneously. We reset the Fibonacci values on a successful connection.

on_video_player_ready

on_video_player_ready(_elem: HTMLElement)

This method is used to initiate several variables, create the WebGL context, and add several event listeners.

  1. We get most of the Div and Link elements in this function via jQuery.

                this.video_player = this.client.videoPlayer;
    
                this.timeLocalDiv = <HTMLDivElement>document.querySelector("#time_local");
                this.timeVideoDiv = <HTMLDivElement>document.querySelector("#time_video");
                this.timeVideoRawDiv = <HTMLDivElement>document.querySelector("#time_video_raw");
                this.timeComposeDiv = <HTMLDivElement>document.querySelector("#time_compose");
                this.timeComposeLastDiv = <HTMLDivElement>document.querySelector("#time_compose_last");
                this.timeStreamDiv = <HTMLDivElement>document.querySelector("#time_stream");
                this.latencyDiv = <HTMLDivElement>document.querySelector("#latency");
                this.delayOffsetDiv = <HTMLDivElement>document.querySelector("#delay_offset");
                this.controlsDiv = <HTMLDivElement>document.querySelector("#game-controls");
    
                this.timeCamSceneDiv = <HTMLDivElement>document.querySelector("#timeCamScene_overlay");
    
                this.promptOverlay = <HTMLDivElement>document.querySelector("#prompt_overlay");
    
                this.videoOverlay = <HTMLDivElement>document.querySelector("#video_overlay");
                this.canvas3d = <HTMLCanvasElement>document.querySelector("#canvas_overlay_3d");
    
                this.mouseOverlay = <HTMLDivElement>document.querySelector("#mouse_overlay");
    
                this.genvidOverlayButton = <HTMLLinkElement>document.querySelector("#genvid_overlay_button");
                this.genvidOverlay = <HTMLDivElement>document.querySelector("#genvid_overlay");
                this.help_overlay = <HTMLDivElement>document.querySelector("#help_overlay");
                this.helpButton = <HTMLLinkElement>document.querySelector("#help_button");
                this.fullScreenDiv = <HTMLDivElement>document.querySelector(".fullscreen-button");
                this.fullScreenElement = <HTMLElement>document.querySelector(".fa-expand");
    
  2. We create the WebGLContext with canvas3d selected.

  3. We add all the window-change event-listeners (fullscreen, resize).

  4. We add the event listeners for the click interaction on the mouseOverlay (for clicking on the video objects), the Genvid button and the Help button.

                this.hideOverlay();
                this.client.videoPlayer.addEventListener(genvid.PlayerEvents.PAUSE, () => {
                    this.hideOverlay();
                });
    
                this.client.videoPlayer.addEventListener(genvid.PlayerEvents.PLAYING, () => {
                    this.showOverlay();
                });
    
                this.genvidWebGL = genvid.createWebGLContext(this.canvas3d); // Need to assign before any resize.
    
                document.addEventListener("fullscreenchange", () => { this.onResize(); });
                document.addEventListener("webkitfullscreenchange", () => { this.onResize(); });
                document.addEventListener("mozfullscreenchange", () => { this.onResize(); });
                _elem.addEventListener("resize", () => { this.onResize(); });
    
                window.addEventListener("resize", () => { this.onResize(); }, true);
                window.addEventListener("orientationchange", () => { this.onResize(); }, true);
                window.addEventListener("sizemodechange", () => { this.onResize(); }, true);
                window.setInterval(() => { this.onResize(); }, 1000); // Just a safety, in case something goes wrong.
    
                this.mouseOverlay.addEventListener("click", (event) => { this.clickCube(event); }, false);
                this.genvidOverlayButton.addEventListener("click", (_event) => { this.toggleGenvidOverlay(); }, false);
                this.helpButton.addEventListener("click", (_event) => { this.onHelpActivation(); }, false);
                this.fullScreenDiv.addEventListener("click", (_event) => { this.toggleFullScreen(); }, false);
    
  5. We do an onResize() to fit the overlays to the size of the window. (This is required otherwise the overlays are too small.)

  6. Finally, we initialize the WebGL overlay.

                this.onResize();
    
                // Initialize graphics stuff.
                this.genvidWebGL.clear();
                let gl = this.genvidWebGL.gl;
                gl.disable(gl.DEPTH_TEST);
    
                this.gfx_initShaders();
                this.gfx_initRenderCommands();
                const muteIcon = document.getElementById("mute-button");
                muteIcon.addEventListener("click", () => this.onMute());
                if (!this.video_player.getMuted()) {
                    this.onMute();
                }
    
                this.videoReady = true;