trailofbits/tubertc

View on GitHub
public/js/room.js

Summary

Maintainability
D
1 day
Test Coverage
/**
 * @file Defines the VTC room logic.
 *
 * @requires module:js/chat
 * @requires module:js/error
 * @requires module:js/navbar
 * @requires module:js/dialog
 * @requires module:js/viewports
 * @requires module:js/audiometer
 * @requires Handlebars.js
 *
 * telemetry/debug.js is optional.
 */

'use strict';

/**
 * Entry point for when the VTC chat is ready to start
 * (after user clicks Join Room button).
 *
 * @param {Object} params - Initialization parameters.
 * @returns {undefined} undefined
 * @public
 */
var vtcMain = function(params) {
    // @todo XXX(debug): If debugging mode is enabled (DebugConsole is a valid function),
    // instantiate a new instance of DebugConsole
    var dbgListener = null;
    if (typeof DebugConsole === 'object' && typeof DebugConsole.Listener === 'function') {
        dbgListener = new DebugConsole.Listener();
    }

    // @todo (input): verify that passing params.roomName to .text() is not susceptible to XSS/etc
    $('#roomNameField')
        .text(params.roomName)
        .fadeIn(function() {
            // Change the browser's URL bar so that people can use it to give out
            // links to other future callers
            history.pushState({}, '', '/?room=' + escape(params.roomName));

            // Fade in the vtcRoom container used for placing the videos
            $('#vtcRoom').fadeIn();
        });

    // Disables camera if the camera button is disabled (because no camera was found)
    if (!params.hasCamera) {
        NavBar.cameraBtn.disableButton();
    }

    // Disables mic if the mic button is disabled (because no mic was found)
    if (!params.hasMic) {
        NavBar.micBtn.disableButton();
    }

    // Set up default VTC user interface state
    if (params.dashIsEnabled) {
        trtcDash.showDashMode();
    } else {
        trtcDash.showHangoutsMode();
    }

    // @todo FIXME: For some reason, media-presence peer messages arrive before onStreamAccepted. This causes media-presence
    //              to be handled correctly since no peerId exists in idToViewPort. To deal with this, we need a queue to
    //              store all the messages for execution later.
    var mediaPresenceMap = {};

    // Instantiate the Chat object
    var chatRoom = new Chat(params.roomName);

    // Maps peerIds to Viewport objects
    var idToViewPort = {};

    /**
     * Helper for sending media presence messages.
     *
     * @param {Object} client - Client instance.
     * @param {String} mediaType - The media type.
     * @param {Boolean} mediaEnabled - True if
     * enabled, false otherwise.
     * @returns {undefined} undefined
     * @private
     */
    var sendMediaPresence = function(client, mediaType, mediaEnabled) {
        client.sendPeerMessage({
            room: params.rtcName
        }, 'media-presence', {
            type: mediaType,
            enabled: mediaEnabled
        });
    };

    /**
     * Helper for handling media presence messages.
     *
     * @param {Object} client - Client instance.
     * @param {String} peerId - The current peer ID.
     * @param {Object} content - Media presence message content.
     * @returns {undefined} undefined
     * @private
     */
    var handleMediaPresence = function(client, peerId, content) {
        var viewport = idToViewPort[peerId];
        if (viewport !== undefined) {
            if (content.type === 'camera') {
                viewport.showCamera(content.enabled);
            } else if (content.type === 'mic') {
                viewport.showMic(content.enabled);
            } else {
                ErrorMetric.log('VTCCore.onPeerMessage => "' + content.type + '" is invalid');
            }
        } else {
            ErrorMetric.log('VTCCore.onPeerMessage => "' + peerId + '" is not a valid key');
        }
    };

    NavBar.cameraBtn.disableButton();
    NavBar.micBtn.disableButton();
    NavBar.dashBtn.disableButton();

    VTCCore
        .initialize({
            cameraIsEnabled: params.hasCamera,
            micIsEnabled: params.hasMic
        })
        .onError(function(config) {
            NavBar.cameraBtn.enableButton();
            NavBar.micBtn.enableButton();
            NavBar.dashBtn.enableButton();

            Dialog.show(config);
        })
        .onPeerMessage(function(client, peerId, msgType, content) {
            if (msgType === 'chat') {
                chatRoom.handlePeerMessage(peerId, content);
            } else if (msgType === 'media-presence' &&
                       typeof content.type === 'string' &&
                       typeof content.enabled === 'boolean') {
                // 'media-presence' peerMessage
                //   Example format:
                //     {
                //       type    : <String>,
                //       enabled : <boolean>
                //     }
                //
                //   Possible types:
                //     'camera' : indicates a change in the camera status from a peer
                //     'mic'    : indicates a change in the mic status from a peer
                if (idToViewPort[peerId] !== undefined) {
                    handleMediaPresence(client, peerId, content);
                } else {
                    if (mediaPresenceMap[peerId] === undefined) {
                        mediaPresenceMap[peerId] = [];
                    }

                    mediaPresenceMap[peerId].push(content);
                }
            } else if (msgType === 'debug') {
                // @todo XXX(debug): handle debug messages
                if (dbgListener !== null) {
                    dbgListener.handlePeerMessage(client, peerId, content);
                } else {
                    ErrorMetric.log('peerMessage => debug message got in non-debug mode!');
                }
            } else if (msgType === 'mic-control' && typeof content.enabled === 'boolean') {
                // @todo FIXME XXX XXX: Might be an issue with this feature. The ability to remotely
                //                      mute/unmute people might not be so cool (namely the unmute part).
                //                      For now, leave it in, but think about disabling the unmute feature
                //                      or having a config flag to deal with it.

                // 'mic-control' peerMessage
                //   Example format:
                //     {
                //       enabled : boolean
                //     }
                //
                if (peerId !== client.getId()) {
                    // Toggle the micBtn only if the requested microphone state is false (microphone disabled)
                    // and the current state is enabled.
                    //
                    // @todo XXX: probably not a good idea to have remote unmute capabilities
                    if (!content.enabled && content.enabled !== NavBar.micBtn.isSelected()) {
                        // clickButton is called because this causes the mute overlay to show up
                        NavBar.micBtn.clickButton();
                    }
                } else {
                    ErrorMetric.log('peerMessage => got a mute request from myself...ignoring');
                }
            } else if (msgType === 'audio-meter') {
                AudioMeter.handlePeerMessage(peerId, content);
            } else {
                // @todo FIXME: right now we don't have other messages to take care of
                ErrorMetric.log('peerMessage => got a peer message that is unexpected');
                ErrorMetric.log('            => peerId:  ' + peerId);
                ErrorMetric.log('            =>   name: ' + client.idToName(peerId));
                ErrorMetric.log('            => msgType: ' + msgType);
                ErrorMetric.log('            => content: ' + JSON.stringify(content));
            }
        })
        .onStreamAccept(function(client, peerId, stream) {
            var peerName = client.idToName(peerId);
            chatRoom.userEntered(peerId, peerName);

            // Create a new viewport and unmute the VideoElement
            var port = trtcDash.createGridForNewUser(peerName);
            port.videoSrc.prop('muted', false);
            client.setVideoObjectSrc(port.videoSrc, stream);

            if (typeof AudioMeter === 'object') {
                AudioMeter.create(peerId, stream, port.audioMeterFill);
            }

            idToViewPort[peerId] = port;

            // Handle deferred requests
            var peerIdMediaPresenceQueue = mediaPresenceMap[peerId];
            if (peerIdMediaPresenceQueue !== undefined) {
                while (peerIdMediaPresenceQueue.length > 0) {
                    var content = peerIdMediaPresenceQueue.shift();
                    handleMediaPresence(client, peerId, content);
                }
                delete mediaPresenceMap[peerId];
            }

            // @todo XXX: send status from navbar buttons
            if (!NavBar.cameraBtn.isSelected()) {
                sendMediaPresence(client, 'camera', false);
            }

            if (!NavBar.micBtn.isSelected()) {
                sendMediaPresence(client, 'mic', false);
            }
        })
        .onStreamClose(function(client, peerId) {
            chatRoom.userLeft(peerId);

            var port = idToViewPort[peerId];
            if (port !== undefined) {
                trtcDash.removeUserWithGrid(port);

                if (typeof AudioMeter === 'object') {
                    AudioMeter.destroy(peerId);
                }

                delete idToViewPort[peerId];
            } else {
                ErrorMetric.log('vtcMain => failed to find viewport for ' + peerId);
            }
        })
        .connect(params.userName, params.rtcName, function(client) {
            var stream = client.getLocalStream();
            var myPeerId = client.getId();

            NavBar.cameraBtn.enableButton();
            NavBar.micBtn.enableButton();
            NavBar.dashBtn.enableButton();

            // @todo XXX(debug): set the VTC client object so that instantiations of DebugConsole.getClient() in the
            //                   JavaScript debugger will work successfully.
            if (dbgListener !== null) {
                DebugConsole.setVtcObject({
                    roomList: idToViewPort,
                    client: client
                });
            }

            // Initialize AudioMeter namespace, this is needed because we need sendPeerMessage functionality
            if (typeof AudioMeter === 'object') {
                AudioMeter.init(client);
            }

            chatRoom
                .initialize(myPeerId, params.userName, function(message) {
                    return client.sendPeerMessage({
                        room: params.rtcName
                    }, 'chat', message);
                })
                .show();

            // Create a viewport for ourself and make it mirrored, hide it initially to ensure
            // a smooth transition if camera is initially disabled
            var viewport = trtcDash.createGridForNewUser();
            viewport.videoSrc
                .css('display', 'none')
                .addClass('video_mirror');
            client.setVideoObjectSrc(viewport.videoSrc, stream);

            if (typeof AudioMeter === 'object') {
                AudioMeter.create(myPeerId, stream, viewport.audioMeterFill, true);
            }

            idToViewPort[myPeerId] = viewport;

            // Only send initial state if they differ from the assumed state (which is enabled)
            if (!params.cameraIsEnabled) {
                client.enableCamera(false);
                sendMediaPresence(client, 'camera', false);
                viewport.showCamera(false);
            } else {
                viewport.showCamera(true);
            }

            if (!params.micIsEnabled) {
                client.enableMicrophone(false);
                sendMediaPresence(client, 'mic', false);
                viewport.showMic(false);
            }

            // Binds actions to the Enable/Disable Camera button
            NavBar.cameraBtn.handle(function() {
                client.enableCamera(true);
                sendMediaPresence(client, 'camera', true);

                viewport.showCamera(true);
            }, function() {
                client.enableCamera(false);
                sendMediaPresence(client, 'camera', false);

                viewport.showCamera(false);
            });

            // Binds actions to the Enable/Disable Microphone button
            NavBar.micBtn.handle(function() {
                client.enableMicrophone(true);
                sendMediaPresence(client, 'mic', true);

                viewport.showMic(true);
            }, function() {
                client.enableMicrophone(false);
                sendMediaPresence(client, 'mic', false);

                viewport.showMic(false);
            });

            NavBar.dashBtn.handle(function() {
                trtcDash.showDashMode();
            }, function() {
                trtcDash.showHangoutsMode();
            });
        });
};