Chromium Code Reviews| Index: remoting/webapp/cast_extension_handler.js |
| diff --git a/remoting/webapp/cast_extension_handler.js b/remoting/webapp/cast_extension_handler.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4073f2b21d46131231c3c27ad5e1dba0c1201c15 |
| --- /dev/null |
| +++ b/remoting/webapp/cast_extension_handler.js |
| @@ -0,0 +1,333 @@ |
| +// 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 |
|
Jamie
2014/08/13 23:21:02
Comma after "session"
aiguha
2014/08/15 07:09:55
Done.
|
| + * 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) { |
|
aiguha
2014/08/13 01:29:34
We discussed calling this CastExtensionMessageProx
Jamie
2014/08/13 23:21:02
Acknowledged.
|
| + this.clientSession_ = clientSession; |
|
Jamie
2014/08/13 23:21:03
Please add @private.
aiguha
2014/08/15 07:09:53
Done.
|
| + |
| + /** @type {chrome.cast.Session} |
| + * @private */ |
|
Jamie
2014/08/13 23:21:02
Nit: For multi-line doc comments, the format you'v
aiguha
2014/08/15 07:09:54
Acknowledged.
|
| + this.session = null; |
|
Jamie
2014/08/13 23:21:03
Private members should have a trailing underscore.
aiguha
2014/08/15 07:09:54
Acknowledged.
|
| + |
| + /** @type {string} |
| + * @private */ |
| + this.chromotingNamespace = 'urn:x-cast:com.chromoting.cast.all'; |
|
Jamie
2014/08/13 23:21:03
If this is a constant, we conventionally prefix th
aiguha
2014/08/15 07:09:54
Done.
|
| + |
| + /** @type {Array} |
|
Jamie
2014/08/13 23:21:03
Can you be more specific about the type? Is it Arr
aiguha
2014/08/15 07:09:54
Done.
|
| + * @private */ |
| + this.messageQueue = []; |
| + |
| + this.start(); |
|
Jamie
2014/08/13 23:21:02
Generally, doing anything non-trivial in the ctor
aiguha
2014/08/15 07:09:54
I think the call is unavoidable, because we need t
Jamie
2014/08/15 18:45:43
You can just call it explicitly immediately after
|
| +}; |
| + |
| +/** |
| + * 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. |
| + */ |
| +remoting.CastExtensionHandler.prototype.start = function() { |
| + var node = document.getElementById(this.SCRIPT_NODE_ID_); |
| + if (node) { |
| + console.error( |
| + '### Multiple calls to CastExtensionHandler.start not expected.'); |
|
aiguha
2014/08/13 01:29:34
The Sender API turns on logging by default, and th
Jamie
2014/08/13 23:21:02
I think we should disable logs by default, especia
aiguha
2014/08/15 07:09:55
Sounds good, I've left in the logs that represent
|
| + 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); |
|
Jamie
2014/08/13 23:21:02
I think this should happen after adding the event
aiguha
2014/08/15 07:09:54
I was following wcs_loader.js's start() exactly he
Jamie
2014/08/15 18:45:43
No, I'm pretty sure there's no race, so it can sta
|
| + |
| + /** @type {remoting.CastExtensionHandler} */ |
| + var that = this; |
| + var onLoad = function() { |
| + window['__onGCastApiAvailable'] = that.onGCastApiAvailable.bind(that); |
| + |
| + }; |
| + var onLoadError = function(event) { |
| + console.error("### Failed to load cast_sender.js."); |
| + } |
| + node.addEventListener('load', onLoad, false); |
| + node.addEventListener('error', onLoadError, false); |
| + |
| +}; |
| + |
| +/** |
| + * Process cast-host-extension messages. |
|
Jamie
2014/08/13 23:21:03
I'm not sure how to parse "cast-host-extension mes
aiguha
2014/08/15 07:09:55
Acknowledged.
|
| + * @param {string} msgString The cast host extension message data. |
| + */ |
| +remoting.CastExtensionHandler.prototype.onMessage = function(msgString) { |
|
Jamie
2014/08/13 23:21:03
I think this and most of the subsequent methods ar
aiguha
2014/08/15 07:09:56
This one is actually called from ClientSession, bu
|
| + var message = getJsonObjectFromString(msgString); |
|
Jamie
2014/08/13 23:21:03
This can throw an exception, which you're not catc
aiguha
2014/08/15 07:09:53
Acknowledged.
Caught above in client_session as
|
| + console.log("### Received Message from Host: ", message); |
| + // 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.chromotingNamespace, |
| + this.messageQueue[i], |
| + this.sendMessageSuccess.bind(this), |
| + this.sendMessageFailure.bind(this)); |
|
Jamie
2014/08/13 23:21:02
Nit: subsequent parameters should be either indent
aiguha
2014/08/15 07:09:54
Acknowledged.
|
| + } |
| + this.messageQueue = []; |
| +}; |
| + |
| +/** |
| + * Event handler for '__onGCastApiAvailable' window event. |
|
Jamie
2014/08/13 23:21:04
Can you provide a bit more context about this even
aiguha
2014/08/15 07:09:53
Done.
|
| + * |
| + * @param {boolean} loaded Represents 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 applicationID = "8A1211E3"; |
|
aiguha
2014/08/13 01:29:34
This Application ID is registered against the cust
Jamie
2014/08/13 23:21:03
I think that's fine. If that was the reason for re
aiguha
2014/08/15 07:09:54
I agree completely! I actually wanted to do that t
|
| + var sessionRequest = new chrome.cast.SessionRequest(applicationID); |
| + 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.log("### Initialization Failed."); |
|
Jamie
2014/08/13 23:21:03
console.error
aiguha
2014/08/15 07:09:54
Done.
|
| +}; |
| + |
| +/** |
| + * 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} e The resulting session, non-nullable. |
|
Jamie
2014/08/13 23:21:02
I think we can probably come up with a more descri
aiguha
2014/08/15 07:09:53
I think I'll follow suit and not bother with it, b
|
| + */ |
| +remoting.CastExtensionHandler.prototype.sessionListener = function(e) { |
| + console.log('### New Session. ID: ' + /** @type {string} */ (e.sessionId)); |
| + this.session = e; |
| + if(this.session.media.length != 0) { |
| + console.log("### Found " + this.session.media.length + " sessions."); |
| + this.onMediaDiscovered('sessionListener', this.session.media[0]); |
|
Jamie
2014/08/13 23:21:02
Why are we only interested in the first session? I
aiguha
2014/08/15 07:09:53
Done.
We only bother with the first because it's
|
| + } |
| + this.session.addMediaListener( |
| + this.onMediaDiscovered.bind(this, 'addMediaListener')); |
| + this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); |
| + this.session.addMessageListener(this.chromotingNamespace, |
| + this.chromotingMessageListener.bind(this)); |
|
Jamie
2014/08/13 23:21:03
Nit: Indentation
aiguha
2014/08/15 07:09:53
Done.
|
| + this.session.sendMessage(this.chromotingNamespace, |
| + {subject : 'test', chromoting_data : 'Hello, Cast.'}, |
| + this.sendMessageSuccess.bind(this), |
| + this.sendMessageFailure.bind(this)); |
|
Jamie
2014/08/13 23:21:03
Nit: Indentation
aiguha
2014/08/15 07:09:55
Done.
|
| + 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.log('### New Media Session ID: ' + media.mediaSessionId); |
| +}; |
|
Jamie
2014/08/13 23:21:04
Do this function need to exist at all? I was assum
aiguha
2014/08/15 07:09:55
It is a callback from the API, but it is also call
|
| + |
| +/** |
| + * Listener invoked when a cast extension message was sent to the cast device |
| + * successfully. |
| + * @private |
| + */ |
| +remoting.CastExtensionHandler.prototype.sendMessageSuccess = function() { |
| + console.log('### Sent Message Successfully.'); |
| +}; |
| + |
| +/** |
| + * 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 was sent to the cast device |
| + * successfully. |
|
Jamie
2014/08/13 23:21:02
This is the same comment as for sendMessageSuccess
aiguha
2014/08/15 07:09:54
Nope, I copied it over and forgot to s/sent to/rec
|
| + * @param {string} ns The namespace of the message received. |
|
Jamie
2014/08/13 23:21:03
You're not using the namespace. Shouldn't you be c
aiguha
2014/08/15 07:09:54
Done.
|
| + * @param {string} message The stringified JSON message received. |
| + */ |
| +remoting.CastExtensionHandler.prototype.chromotingMessageListener = |
| + function(ns, message) { |
| + var messageObj = getJsonObjectFromString(message); |
| + console.log("### Received Message from Cast: " + ns + "-" + messageObj); |
| + this.sendMessageToHost_(messageObj); |
| +}; |
| + |
| +/** |
| + * 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} e Describes receiver availability. |
|
Jamie
2014/08/13 23:21:02
Parameter name.
aiguha
2014/08/15 07:09:54
Done.
|
| + */ |
| +remoting.CastExtensionHandler.prototype.receiverListener = function(e) { |
| + if( e === chrome.cast.ReceiverAvailability.AVAILABLE) { |
|
Jamie
2014/08/13 23:21:03
Space after 'if', not before 'e'.
aiguha
2014/08/15 07:09:54
Done.
|
| + console.log("### Receiver(s) Found."); |
| + } |
| + else { |
|
Jamie
2014/08/13 23:21:02
No newline before 'else'.
aiguha
2014/08/15 07:09:55
Done.
|
| + console.log("### No Receivers Available."); |
| + } |
| +}; |
| + |
| +/** |
| + * Launches the associated receiver application by requesting that it be created |
| + * or joined on the Cast device. By default, the SessionRequest passed during |
| + * initialization is used. |
|
Jamie
2014/08/13 23:21:02
I don't really understand what this function does.
aiguha
2014/08/15 07:09:55
Clarified the comment. It is supposed to use the S
|
| + * 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() { |
| + console.log("### Launching Cast App."); |
| + chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), |
| + this.onLaunchError.bind(this)); |
| +}; |
| + |
| +/** |
| + * Listener invoked when the chrome.cast.requestSession is successful. |
|
Jamie
2014/08/13 23:21:03
s/the//
s/is successful/completes successfully/
aiguha
2014/08/15 07:09:55
Done.
|
| + * |
| + * @param {chrome.cast.Session} e The requested session. |
| + */ |
| +remoting.CastExtensionHandler.prototype.onRequestSessionSuccess = function (e) { |
| + console.log("### Successfully created session: " + e.sessionId); |
| + this.session = e; |
| + 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.chromotingNamespace, |
| + this.chromotingMessageListener.bind(this)); |
| +}; |
| + |
| +/** |
| + * Listener invoked when the launchApp() 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() { |
| + console.log('### Session Stopped.'); |
| +}; |
| + |
| +/** |
| + * 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); |
| +}; |