Source: newscast.js

/**
 * Newscast: simple Chromecast apps.
 *
 * @namespace Newscast
 */

/* global module */
/* global console */
/* global chrome */
/* global cast */

(function(factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof module !== 'undefined' && module.exports) {
        module.exports = factory();
    } else {
        window.Newscast = factory.call(this);
    }
})(function() {
    var MESSAGE_DELIMITER = 'NEWSCAST';
    var MESSAGE_REGEX = new RegExp('(\\S+)' + MESSAGE_DELIMITER + '(.+)$');
  
    /**
     * The Chromecast receiver that runs on the device.
     *
     * @memberof Newscast
     * @class Receiver
     * @param {Object} config Configuration object
     * @param {String} config.namespace Chromecast namespace for this application.
     * @param {Boolean} config.debug If true, debug information will be logged to the console.
     */
    var Receiver = function(config) {
        var _config = config;

        var _customMessageBus = null;
        var _senderId = null;
        var _messageHandlers = {};

        /*
         * Log a debugging message.
         */
        var _log = function(message, raw) {
            if (config['debug']) {
                if (!raw) {
                    message = 'Newscast.Receiver: ' + message;
                }

                console.log(message);
            }
        };

        /*
         * Receiver ready.
         */
        var _onCastReceiverReady = function(e) {
            _senderId = e.data.launchingSenderId;
            
            _log('Got sender id: ' + _senderId);
        };

        /*
         * New message received.
         */
        var _onReceiveMessage = function(e) {
            _log('Received message: ' + e.data);

            var match = e.data.match(MESSAGE_REGEX);

            var messageType = match[1];
            var message = match[2];

            _fire(messageType, message);
        };

        /*;
         * Fire handler callbacks for a given message.
         */
        var _fire = function(messageType, message) {
            if (messageType in _messageHandlers) {
                for (var i = 0; i < _messageHandlers[messageType].length; i++) {
                    _messageHandlers[messageType][i](message);
                }
            }
        };

        /**
         * Register a new message handler callback.
         *
         * @memberof Newscast.Receiver
         * @method #onMessage
         * @param {String} messageType Name of message type to listen for.
         * @param {Receiver~onMessageCallback} callback The callback to invoke when the given message type is received.
         */
        var onMessage = function(messageType, callback) {
            if (!(messageType in _messageHandlers)) {
                _messageHandlers[messageType] = [];
            }

            _messageHandlers[messageType].push(callback);
        };

        /**
         * @callback Parent~onMessageCallback
         * @param {String} message The message data.
         */

        /**
         * Send a message to the Sender.
         *
         * @memberof Newscast.Receiver
         * @method #sendMessage
         * @param {String} messageType Name of the message type to send.
         * @param {String} message Message data to send.
         */
        var sendMessage = function(messageType, message) {
            message = messageType + MESSAGE_DELIMITER + message;

            _log('Sending message: ' + message);
            
            _customMessageBus.send(
                _senderId,
                message
            );
        };

        _log('Initializing receiver');

        var castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
        _customMessageBus = castReceiverManager.getCastMessageBus(_config['namespace']);

        castReceiverManager.onReady = _onCastReceiverReady;
        _customMessageBus.onMessage = _onReceiveMessage; 

        castReceiverManager.start();

        return {
            'onMessage': onMessage,
            'sendMessage': sendMessage
        };
    };

    /**
     * The Chromecast app Sender that runs in the user's browser. 
     *
     * @memberof Newscast
     * @class Sender
     * @param {Object} config Configuration object
     * @param {String} config.namespace Chromecast namespace for this application.
     * @param {String} config.appId Chromecast application identifier.
     * @param {Sender~onSenderReadyCallback} config.onSenderReady Callback to be fired when a device is available to be cast to.
     * @param {Sender~onSenderStartedCallback} config.onSenderStarted Callback to be fired when casting has begun.
     * @param {Sender~onSenderStoppedCallback} config.onSenderStopped Callback to be fired when casting has ceased.
     * @param {Boolean} config.debug If true, debug information will be logged to the console.
     */
    var Sender = function(config) {
        var _config = config;

        var _session = null;
        var _messageHandlers = {};

        /*
         * Log a debugging message.
         */
        var _log = function(message, raw) {
            if (config['debug']) {
                if (!raw) {
                    message = 'Newscast.Sender: ' + message;
                }

                console.log(message);
            }
        };

        /*
         * Listen for existing sessions with the receiver.
         */
        var _sessionListener = function(session) {
            _log('Session created');

            _session = session;
            _session.addUpdateListener(_sessionUpdateListener);

            if (config['onSenderStarted']) {
                config['onSenderStarted']();
            }
        };

        /*
         * Listen for changes to the session status.
         */
        var _sessionUpdateListener = function(isAlive) {
            if (!isAlive) {
                _log('Session no longer alive');

                if (config['onSenderStopped']) {
                    config['onSenderStopped']();
                }
            }
        };

        /*
         * Listen for receivers to become available.
         */
        var _receiverListener = function(e) {
            if (e === chrome.cast.ReceiverAvailability.AVAILABLE) {
                _log('Receiver is available');

                if (config['onSenderReady']) {
                    config['onSenderReady']();
                }
            } else if (e === chrome.cast.ReceiverAvailability.UNAVAILABLE) {
                _log('Receiver not available');
                _log(e, true);
            }
        };

        /**
         * @callback Sender~onSenderReadyCallback
         */

        /*
         * Environment successfully initialized.
         */
        var _onInitSuccess = function() {
            _log('Chromecast initialized');
        };

        /*
         * Error initializing.
         */
        var _onInitError = function(e) {
            _log('Chromecast initialization failed, error:');
            _log(e, true);
        };

        /**
         * Request to start a Chromecasting session. Will cause the
         * browser plugin device selection dialog to open.
         *
         * @memberof Newscast.Sender
         * @method #startCasting
         */
        var startCasting = function() {
            _log('Starting cast');

            chrome.cast.requestSession(_onRequestSessionSuccess, _onRequestSessionError);
        };

        /*
         * Casting session begun successfully.
         */
        var _onRequestSessionSuccess = function(session) {
            _log('Session created');

            _session = session;
            _session.addUpdateListener(_sessionUpdateListener);
            _session.addMessageListener(_config['namespace'], _onReceiveMessage); 

            if (_config['onSenderStarted']) {
                _config['onSenderStarted']();
            }
        };

        /**
         * @callback Sender~onSenderStartedCallback
         */

        /*
         * Casting session failed to start.
         */
        var _onRequestSessionError = function(e) {
            _log('Failed to create session, error:');
            _log(e, true);
        };

        /**
         * Stop casting an ongoing Chromecast session.
         *
         * @memberof Newscast.Sender
         * @method #stopCasting
         */
        var stopCasting = function() {
            _log('Stopping cast');

            _session.stop(_onSessionStopSuccess, _onSessionStopError);
        };

        /*
         * Inform client the session has stopped.
         */
        var _onSessionStopSuccess = function() {
            _log('Cast stopped');

            if (config['onSenderStopped']) {
                config['onSenderStopped']();
            }
        };

        /**
         * @callback Sender~onSenderStoppedCallback
         */

        /*
         * Session could not be stopped.
         */
        var _onSessionStopError = function(e) {
            _log('Failed to stop cast, error:');
            _log(e, true);
        };

        /*
         * New message received.
         */
        var _onReceiveMessage = function(namespace, data) {
            _log('Received message: ' + data);

            var match = data.match(MESSAGE_REGEX);

            var messageType = match[1];
            var message = match[2];

            _fire(messageType, message);
        };

        /*
         * Fire handler callbacks for a given message.
         */
        var _fire = function(messageType, message) {
            if (messageType in _messageHandlers) {
                for (var i = 0; i < _messageHandlers[messageType].length; i++) {
                    _messageHandlers[messageType][i](message);
                }
            }
        };

        /**
         * Register a new message handler callback.
         *
         * @memberof Newscast.Sender 
         * @method #onMessage
         * @param {String} messageType Name of message type to listen for.
         * @param {Sender~onMessageCallback} callback The callback to invoke when the given message type is received.
         */
        var onMessage = function(messageType, callback) {
            if (!(messageType in _messageHandlers)) {
                _messageHandlers[messageType] = [];
            }

            _messageHandlers[messageType].push(callback);
        };

        /**
         * @callback Sender~onMessageCallback
         * @param {String} message The message data.
         */

        /**
         * Send a message to the Receiver.
         *
         * @memberof Newscast.Sender
         * @method #sendMessage
         * @param {String} messageType Name of the message type to send.
         * @param {String} message Message data to send.
         */
        var sendMessage = function(messageType, message) {
            message = messageType + MESSAGE_DELIMITER + message;
            
            _log('Sending message: ' + message);

            _session.sendMessage(
                config['namespace'],
                message,
                _onSendSuccess,
                _onSendError
            );
        };

        /*
         * Successfully sent message to receiver.
         */
        var _onSendSuccess = function() {
            _log('Message sent');
        };

        /*
         * Error sending message to receiver.
         */
        var _onSendError = function(e) {
            _log('Failed to send message, error:');
            _log(e, true);
        };

        _log('Initializing sender');

        var sessionRequest = new chrome.cast.SessionRequest(config['appId']);

        var apiConfig = new chrome.cast.ApiConfig(
            sessionRequest,
            _sessionListener,
            _receiverListener,
            chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
        );

        chrome.cast.initialize(apiConfig, _onInitSuccess, _onInitError);

        return {
            'startCasting': startCasting,
            'stopCasting': stopCasting,
            'sendMessage': sendMessage,
            'onMessage': onMessage
        };
    };

    /**
     * Creates a simple Chromecast app all on one page with full bi-directional communication.
     *
     * @memberof Newscast
     * @class Newscast
     * @param {Object} config Configuration object
     * @param {String} config.namespace Chromecast namespace for this application.
     * @param {String} config.appId Chromecast application identifier.
     * @param {Newscast.Newscast~onSenderCreatedCallback} config.onSenderCreated Callback to be fired when a Sender instance is created.
     * @param {Newscast.Newscast~onReceiverCreatedCallback} config.onReceiverCreated Callback to be fired when a Receiver instance is created.
     * @param {Sender~onSenderReadyCallback} config.onSenderReady Callback to be fired when a device is available to be cast to.
     * @param {Sender~onSenderStartedCallback} config.onSenderStarted Callback to be fired when casting has begun.
     * @param {Sender~onSenderStoppedCallback} config.onSenderStopped Callback to be fired when casting has ceased.
     * @param {Boolean} config.debug If true, debug information will be logged to the console.

     */
    var Newscast = function(config) {
        var _config = config;

        if (!config.hasOwnProperty('isReceiver')) {
            config['isReceiver'] = (window.location.search.indexOf('newscast-receiver=true') >= 0);
        }

        /*
         * Log a debugging message.
         */
        var _log = function(message, raw) {
            if (config['debug']) {
                if (!raw) {
                    message = 'Newscast.Newcast: ' + message;
                }

                console.log(message);
            }
        };

        /**
         * @callback Newscast.Newscast~onSenderCreatedCallback
         * @param {Sender} sender A Sender instance.
         */

        /**
         * @callback Newscast.Newscast~onReceiverCreatedCallback
         * @param {Receiver} receiver A Receiver instance.
         */

        var script =  document.createElement('script');
        script.async = false;
        script.type = 'text/javascript';

        if (config['isReceiver']) {
            // Load Receiver library
            script.src = '//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js';

            script.onload = function() {
                // Create receiver
                var receiver = new Receiver(_config);

                config['onReceiverCreated'](receiver);

            };

            document.body.appendChild(script);
        } else {
            // Setup Cast Sender API global callback
            window['__onGCastApiAvailable'] = function(loaded, errorInfo) {
                if (loaded) {
                    // Create sender
                    var sender = new Sender(_config);

                    config['onSenderCreated'](sender);
                } else {
                   _log('Failed to load Chromecast library, error:');
                   _log(errorInfo, true);
                }
            };
            
            // Load Sender API
            script.src = '//www.gstatic.com/cv/js/sender/v1/cast_sender.js';
            document.body.appendChild(script);
        }
    };

    return {
        'Receiver': Receiver,
        'Sender': Sender,
        'Newscast': Newscast
    };
});