blob: d3a09240559c1a261eed41f35b853395d587a2a8 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Description of this file.
* Class handling interaction with the cast extension session of the Chromoting
* host. It receives and sends extension messages from/to the host through
* the client session. It uses the Google Cast Chrome Sender API library to
* interact with nearby Cast receivers.
*
* Once it establishes connection with a Cast device (upon user choice), it
* creates a session, loads our registered receiver application and then becomes
* a message proxy between the host and cast device, helping negotiate
* their peer connection.
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* @constructor
* @param {!remoting.ClientSession} clientSession The client session to send
* cast extension messages to.
*/
remoting.CastExtensionHandler = function(clientSession) {
/** @private */
this.clientSession_ = clientSession;
/** @type {chrome.cast.Session} @private */
this.session_ = null;
/** @type {string} @private */
this.kCastNamespace_ = 'urn:x-cast:com.chromoting.cast.all';
/** @type {string} @private */
this.kApplicationId_ = "8A1211E3";
/** @type {Array.<Object>} @private */
this.messageQueue_ = [];
this.start_();
};
/**
* The id of the script node.
* @type {string}
* @private
*/
remoting.CastExtensionHandler.prototype.SCRIPT_NODE_ID_ = 'cast-script-node';
/**
* Attempts to load the Google Cast Chrome Sender API libary.
* @private
*/
remoting.CastExtensionHandler.prototype.start_ = function() {
var node = document.getElementById(this.SCRIPT_NODE_ID_);
if (node) {
console.error(
'Multiple calls to CastExtensionHandler.start_ not expected.');
return;
}
// Create a script node to load the Cast Sender API.
node = document.createElement('script');
node.id = this.SCRIPT_NODE_ID_;
node.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js";
node.type = 'text/javascript';
document.body.insertBefore(node, document.body.firstChild);
/** @type {remoting.CastExtensionHandler} */
var that = this;
var onLoad = function() {
window['__onGCastApiAvailable'] = that.onGCastApiAvailable.bind(that);
};
var onLoadError = function(event) {
console.error("Failed to load Chrome Cast Sender API.");
}
node.addEventListener('load', onLoad, false);
node.addEventListener('error', onLoadError, false);
};
/**
* Process Cast Extension Messages from the Chromoting host.
* @param {string} msgString The extension message's data.
*/
remoting.CastExtensionHandler.prototype.onMessage = function(msgString) {
var message = getJsonObjectFromString(msgString);
// Save messages to send after a session is established.
this.messageQueue_.push(message);
// Trigger the sending of pending messages, followed by the one just
// received.
if (this.session_) {
this.sendPendingMessages_();
}
};
/**
* Send cast-extension messages through the client session.
* @param {Object} response The JSON response to be sent to the host. The
* response object must contain the appropriate keys.
* @private
*/
remoting.CastExtensionHandler.prototype.sendMessageToHost_ =
function(response) {
this.clientSession_.sendCastExtensionMessage(response);
};
/**
* Send pending messages from the host to the receiver app.
* @private
*/
remoting.CastExtensionHandler.prototype.sendPendingMessages_ = function() {
var len = this.messageQueue_.length;
for(var i = 0; i<len; i++) {
this.session_.sendMessage(this.kCastNamespace_,
this.messageQueue_[i],
this.sendMessageSuccess.bind(this),
this.sendMessageFailure.bind(this));
}
this.messageQueue_ = [];
};
/**
* Event handler for '__onGCastApiAvailable' window event. This event is
* triggered if the Google Cast Chrome Sender API is available. We attempt to
* load this API in this.start(). If the API loaded successfully, we can proceed
* to initialize it and configure it to launch our Cast Receiver Application.
*
* @param {boolean} loaded True if the API loaded succesfully.
* @param {Object} errorInfo Info if the API load failed.
*/
remoting.CastExtensionHandler.prototype.onGCastApiAvailable =
function(loaded, errorInfo) {
if (loaded) {
this.initializeCastApi();
} else {
console.log(errorInfo);
}
};
/**
* Initialize the Cast API.
* @private
*/
remoting.CastExtensionHandler.prototype.initializeCastApi = function() {
var sessionRequest = new chrome.cast.SessionRequest(this.kApplicationId_);
var apiConfig =
new chrome.cast.ApiConfig(sessionRequest,
this.sessionListener.bind(this),
this.receiverListener.bind(this),
chrome.cast.AutoJoinPolicy.PAGE_SCOPED,
chrome.cast.DefaultActionPolicy.CREATE_SESSION);
chrome.cast.initialize(
apiConfig, this.onInitSuccess.bind(this), this.onInitError.bind(this));
};
/**
* Callback for successful initialization of the Cast API.
*/
remoting.CastExtensionHandler.prototype.onInitSuccess = function() {
console.log("Initialization Successful.");
};
/**
* Callback for failed initialization of the Cast API.
*/
remoting.CastExtensionHandler.prototype.onInitError = function() {
console.error("Initialization Failed.");
};
/**
* Listener invoked when a session is created or connected by the SDK.
* Note: The requestSession method would not cause this callback to be invoked
* since it is passed its own listener.
* @param {chrome.cast.Session} session The resulting session.
*/
remoting.CastExtensionHandler.prototype.sessionListener = function(session) {
console.log('New Session:' + /** @type {string} */ (session.sessionId));
this.session_ = session;
if (this.session_.media.length != 0) {
// There should be no media associated with the session, since we never
// directly load media from the Sender application.
this.onMediaDiscovered('sessionListener', this.session_.media[0]);
}
this.session_.addMediaListener(
this.onMediaDiscovered.bind(this, 'addMediaListener'));
this.session_.addUpdateListener(this.sessionUpdateListener.bind(this));
this.session_.addMessageListener(this.kCastNamespace_,
this.chromotingMessageListener.bind(this));
this.session_.sendMessage(this.kCastNamespace_,
{subject : 'test', chromoting_data : 'Hello, Cast.'},
this.sendMessageSuccess.bind(this),
this.sendMessageFailure.bind(this));
this.sendPendingMessages_();
};
/**
* Listener invoked when a media session is created by another sender.
* @param {string} how How this callback was triggered.
* @param {chrome.cast.media.Media} media The media item discovered.
* @private
*/
remoting.CastExtensionHandler.prototype.onMediaDiscovered =
function(how, media) {
console.error("Unexpected media session discovered.");
};
/**
* Listener invoked when a cast extension message was sent to the cast device
* successfully.
* @private
*/
remoting.CastExtensionHandler.prototype.sendMessageSuccess = function() {
};
/**
* Listener invoked when a cast extension message failed to be sent to the cast
* device.
* @param {Object} error The error.
* @private
*/
remoting.CastExtensionHandler.prototype.sendMessageFailure = function(error) {
console.error('Failed to Send Message.', error);
};
/**
* Listener invoked when a cast extension message is received from the Cast
* device.
* @param {string} ns The namespace of the message received.
* @param {string} message The stringified JSON message received.
*/
remoting.CastExtensionHandler.prototype.chromotingMessageListener =
function(ns, message) {
if (ns === this.kCastNamespace_) {
try {
var messageObj = getJsonObjectFromString(message);
this.sendMessageToHost_(messageObj);
} catch (err) {
console.error('Failed to process message from Cast device.');
}
} else {
console.error("Unexpected message from Cast device.");
}
};
/**
* Listener invoked when there updates to the current session.
*
* @param {boolean} isAlive True if the session is still alive.
*/
remoting.CastExtensionHandler.prototype.sessionUpdateListener =
function(isAlive) {
var message = isAlive ? 'Session Updated' : 'Session Removed';
message += ': ' + this.session_.sessionId +'.';
console.log(message);
};
/**
* Listener invoked when the availability of a Cast receiver that supports
* the application in sessionRequest is known or changes.
*
* @param {chrome.cast.ReceiverAvailability} availability Receiver availability.
*/
remoting.CastExtensionHandler.prototype.receiverListener =
function(availability) {
if (availability === chrome.cast.ReceiverAvailability.AVAILABLE) {
console.log("Receiver(s) Found.");
} else {
console.error("No Receivers Available.");
}
};
/**
* Launches the associated receiver application by requesting that it be created
* on the Cast device. It uses the SessionRequest passed during initialization
* to determine what application to launch on the Cast device.
*
* Note: This method is intended to be used as a click listener for a custom
* cast button on the webpage. We currently use the default cast button in
* Chrome, so this method is unused.
*/
remoting.CastExtensionHandler.prototype.launchApp = function() {
chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this),
this.onLaunchError.bind(this));
};
/**
* Listener invoked when chrome.cast.requestSession completes successfully.
*
* @param {chrome.cast.Session} session The requested session.
*/
remoting.CastExtensionHandler.prototype.onRequestSessionSuccess =
function (session) {
this.session_ = session;
this.session_.addUpdateListener(this.sessionUpdateListener.bind(this));
if (this.session_.media.length != 0) {
this.onMediaDiscovered('onRequestSession', this.session_.media[0]);
}
this.session_.addMediaListener(
this.onMediaDiscovered.bind(this, 'addMediaListener'));
this.session_.addMessageListener(this.kCastNamespace_,
this.chromotingMessageListener.bind(this));
};
/**
* Listener invoked when chrome.cast.requestSession fails.
* @param {chrome.cast.Error} error The error code.
*/
remoting.CastExtensionHandler.prototype.onLaunchError = function(error) {
console.error("Error Casting to Receiver.", error);
};
/**
* Stops the running receiver application associated with the session.
* TODO(aiguha): When the user disconnects using the blue drop down bar,
* the client session should notify the CastExtensionHandler, which should
* call this method to close the session with the Cast device.
*/
remoting.CastExtensionHandler.prototype.stopApp = function() {
this.session_.stop(this.onStopAppSuccess.bind(this),
this.onStopAppError.bind(this));
};
/**
* Listener invoked when the receiver application is stopped successfully.
*/
remoting.CastExtensionHandler.prototype.onStopAppSuccess = function() {
};
/**
* Listener invoked when we fail to stop the receiver application.
*
* @param {chrome.cast.Error} error The error code.
*/
remoting.CastExtensionHandler.prototype.onStopAppError = function(error) {
console.error('Error Stopping App: ', error);
};