Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview Description of this file. | |
| 7 * Class handling interaction with the cast extension session of the Chromoting | |
| 8 * host. It receives and sends extension messages from/to the host through | |
| 9 * the client session. It uses the Google Cast Chrome Sender API library to | |
| 10 * interact with nearby Cast receivers. | |
| 11 * | |
| 12 * Once it establishes connection with a Cast device (upon user choice), it | |
| 13 * 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.
| |
| 14 * a message proxy between the host and cast device, helping negotiate | |
| 15 * their peer connection. | |
| 16 */ | |
| 17 | |
| 18 'use strict'; | |
| 19 | |
| 20 /** @suppress {duplicate} */ | |
| 21 var remoting = remoting || {}; | |
| 22 | |
| 23 /** | |
| 24 * @constructor | |
| 25 * @param {!remoting.ClientSession} clientSession The client session to send | |
| 26 * cast extension messages to. | |
| 27 */ | |
| 28 remoting.CastExtensionHandler = function(clientSession) { | |
|
aiguha
2014/08/13 01:29:34
We discussed calling this CastExtensionMessageProx
Jamie
2014/08/13 23:21:02
Acknowledged.
| |
| 29 this.clientSession_ = clientSession; | |
|
Jamie
2014/08/13 23:21:03
Please add @private.
aiguha
2014/08/15 07:09:53
Done.
| |
| 30 | |
| 31 /** @type {chrome.cast.Session} | |
| 32 * @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.
| |
| 33 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.
| |
| 34 | |
| 35 /** @type {string} | |
| 36 * @private */ | |
| 37 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.
| |
| 38 | |
| 39 /** @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.
| |
| 40 * @private */ | |
| 41 this.messageQueue = []; | |
| 42 | |
| 43 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
| |
| 44 }; | |
| 45 | |
| 46 /** | |
| 47 * The id of the script node. | |
| 48 * @type {string} | |
| 49 * @private | |
| 50 */ | |
| 51 remoting.CastExtensionHandler.prototype.SCRIPT_NODE_ID_ = 'cast-script-node'; | |
| 52 | |
| 53 /** | |
| 54 * Attempts to load the Google Cast Chrome Sender API libary. | |
| 55 */ | |
| 56 remoting.CastExtensionHandler.prototype.start = function() { | |
| 57 var node = document.getElementById(this.SCRIPT_NODE_ID_); | |
| 58 if (node) { | |
| 59 console.error( | |
| 60 '### 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
| |
| 61 return; | |
| 62 } | |
| 63 | |
| 64 // Create a script node to load the Cast Sender API. | |
| 65 node = document.createElement('script'); | |
| 66 node.id = this.SCRIPT_NODE_ID_; | |
| 67 node.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"; | |
| 68 node.type = 'text/javascript'; | |
| 69 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
| |
| 70 | |
| 71 /** @type {remoting.CastExtensionHandler} */ | |
| 72 var that = this; | |
| 73 var onLoad = function() { | |
| 74 window['__onGCastApiAvailable'] = that.onGCastApiAvailable.bind(that); | |
| 75 | |
| 76 }; | |
| 77 var onLoadError = function(event) { | |
| 78 console.error("### Failed to load cast_sender.js."); | |
| 79 } | |
| 80 node.addEventListener('load', onLoad, false); | |
| 81 node.addEventListener('error', onLoadError, false); | |
| 82 | |
| 83 }; | |
| 84 | |
| 85 /** | |
| 86 * 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.
| |
| 87 * @param {string} msgString The cast host extension message data. | |
| 88 */ | |
| 89 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
| |
| 90 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
| |
| 91 console.log("### Received Message from Host: ", message); | |
| 92 // Save messages to send after a session is established. | |
| 93 this.messageQueue.push(message); | |
| 94 // Trigger the sending of pending messages, followed by the one just | |
| 95 // received. | |
| 96 if(this.session) { | |
| 97 this.sendPendingMessages(); | |
| 98 } | |
| 99 }; | |
| 100 | |
| 101 /** | |
| 102 * Send cast-extension messages through the client session. | |
| 103 * @param {Object} response The JSON response to be sent to the host. The | |
| 104 * response object must contain the appropriate keys. | |
| 105 * @private | |
| 106 */ | |
| 107 remoting.CastExtensionHandler.prototype.sendMessageToHost_ = | |
| 108 function(response) { | |
| 109 this.clientSession_.sendCastExtensionMessage(response); | |
| 110 }; | |
| 111 | |
| 112 /** | |
| 113 * Send pending messages from the host to the receiver app. | |
| 114 * @private | |
| 115 */ | |
| 116 remoting.CastExtensionHandler.prototype.sendPendingMessages = function() { | |
| 117 var len = this.messageQueue.length; | |
| 118 for(var i = 0; i<len; i++) { | |
| 119 this.session.sendMessage(this.chromotingNamespace, | |
| 120 this.messageQueue[i], | |
| 121 this.sendMessageSuccess.bind(this), | |
| 122 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.
| |
| 123 } | |
| 124 this.messageQueue = []; | |
| 125 }; | |
| 126 | |
| 127 /** | |
| 128 * 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.
| |
| 129 * | |
| 130 * @param {boolean} loaded Represents if the API loaded succesfully. | |
| 131 * @param {Object} errorInfo Info if the API load failed. | |
| 132 */ | |
| 133 remoting.CastExtensionHandler.prototype.onGCastApiAvailable = | |
| 134 function(loaded, errorInfo) { | |
| 135 if (loaded) { | |
| 136 this.initializeCastApi(); | |
| 137 } else { | |
| 138 console.log(errorInfo); | |
| 139 } | |
| 140 }; | |
| 141 | |
| 142 /** | |
| 143 * Initialize the Cast API. | |
| 144 * @private | |
| 145 */ | |
| 146 remoting.CastExtensionHandler.prototype.initializeCastApi = function() { | |
| 147 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
| |
| 148 var sessionRequest = new chrome.cast.SessionRequest(applicationID); | |
| 149 var apiConfig = | |
| 150 new chrome.cast.ApiConfig(sessionRequest, | |
| 151 this.sessionListener.bind(this), | |
| 152 this.receiverListener.bind(this), | |
| 153 chrome.cast.AutoJoinPolicy.PAGE_SCOPED, | |
| 154 chrome.cast.DefaultActionPolicy.CREATE_SESSION); | |
| 155 chrome.cast.initialize( | |
| 156 apiConfig, this.onInitSuccess.bind(this), this.onInitError.bind(this)); | |
| 157 }; | |
| 158 | |
| 159 /** | |
| 160 * Callback for successful initialization of the Cast API. | |
| 161 */ | |
| 162 remoting.CastExtensionHandler.prototype.onInitSuccess = function() { | |
| 163 console.log("### Initialization Successful."); | |
| 164 }; | |
| 165 | |
| 166 /** | |
| 167 * Callback for failed initialization of the Cast API. | |
| 168 */ | |
| 169 remoting.CastExtensionHandler.prototype.onInitError = function() { | |
| 170 console.log("### Initialization Failed."); | |
|
Jamie
2014/08/13 23:21:03
console.error
aiguha
2014/08/15 07:09:54
Done.
| |
| 171 }; | |
| 172 | |
| 173 /** | |
| 174 * Listener invoked when a session is created or connected by the SDK. | |
| 175 * Note: The requestSession method would not cause this callback to be invoked | |
| 176 * since it is passed its own listener. | |
| 177 * @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
| |
| 178 */ | |
| 179 remoting.CastExtensionHandler.prototype.sessionListener = function(e) { | |
| 180 console.log('### New Session. ID: ' + /** @type {string} */ (e.sessionId)); | |
| 181 this.session = e; | |
| 182 if(this.session.media.length != 0) { | |
| 183 console.log("### Found " + this.session.media.length + " sessions."); | |
| 184 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
| |
| 185 } | |
| 186 this.session.addMediaListener( | |
| 187 this.onMediaDiscovered.bind(this, 'addMediaListener')); | |
| 188 this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); | |
| 189 this.session.addMessageListener(this.chromotingNamespace, | |
| 190 this.chromotingMessageListener.bind(this)); | |
|
Jamie
2014/08/13 23:21:03
Nit: Indentation
aiguha
2014/08/15 07:09:53
Done.
| |
| 191 this.session.sendMessage(this.chromotingNamespace, | |
| 192 {subject : 'test', chromoting_data : 'Hello, Cast.'}, | |
| 193 this.sendMessageSuccess.bind(this), | |
| 194 this.sendMessageFailure.bind(this)); | |
|
Jamie
2014/08/13 23:21:03
Nit: Indentation
aiguha
2014/08/15 07:09:55
Done.
| |
| 195 this.sendPendingMessages(); | |
| 196 }; | |
| 197 | |
| 198 /** | |
| 199 * Listener invoked when a media session is created by another sender. | |
| 200 * @param {string} how How this callback was triggered. | |
| 201 * @param {chrome.cast.media.Media} media The media item discovered. | |
| 202 * @private | |
| 203 */ | |
| 204 remoting.CastExtensionHandler.prototype.onMediaDiscovered = | |
| 205 function(how, media) { | |
| 206 console.log('### New Media Session ID: ' + media.mediaSessionId); | |
| 207 }; | |
|
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
| |
| 208 | |
| 209 /** | |
| 210 * Listener invoked when a cast extension message was sent to the cast device | |
| 211 * successfully. | |
| 212 * @private | |
| 213 */ | |
| 214 remoting.CastExtensionHandler.prototype.sendMessageSuccess = function() { | |
| 215 console.log('### Sent Message Successfully.'); | |
| 216 }; | |
| 217 | |
| 218 /** | |
| 219 * Listener invoked when a cast extension message failed to be sent to the cast | |
| 220 * device. | |
| 221 * @param {Object} error The error. | |
| 222 * @private | |
| 223 */ | |
| 224 remoting.CastExtensionHandler.prototype.sendMessageFailure = function(error) { | |
| 225 console.error('### Failed to Send Message.', error); | |
| 226 }; | |
| 227 | |
| 228 /** | |
| 229 * Listener invoked when a cast extension message was sent to the cast device | |
| 230 * 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
| |
| 231 * @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.
| |
| 232 * @param {string} message The stringified JSON message received. | |
| 233 */ | |
| 234 remoting.CastExtensionHandler.prototype.chromotingMessageListener = | |
| 235 function(ns, message) { | |
| 236 var messageObj = getJsonObjectFromString(message); | |
| 237 console.log("### Received Message from Cast: " + ns + "-" + messageObj); | |
| 238 this.sendMessageToHost_(messageObj); | |
| 239 }; | |
| 240 | |
| 241 /** | |
| 242 * Listener invoked when there updates to the current session. | |
| 243 * | |
| 244 * @param {boolean} isAlive True if the session is still alive. | |
| 245 */ | |
| 246 remoting.CastExtensionHandler.prototype.sessionUpdateListener = | |
| 247 function(isAlive) { | |
| 248 var message = isAlive ? '### Session Updated' : '### Session Removed'; | |
| 249 message += ': ' + this.session.sessionId +'.'; | |
| 250 console.log(message); | |
| 251 }; | |
| 252 | |
| 253 /** | |
| 254 * Listener invoked when the availability of a Cast receiver that supports | |
| 255 * the application in sessionRequest is known or changes. | |
| 256 * | |
| 257 * @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.
| |
| 258 */ | |
| 259 remoting.CastExtensionHandler.prototype.receiverListener = function(e) { | |
| 260 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.
| |
| 261 console.log("### Receiver(s) Found."); | |
| 262 } | |
| 263 else { | |
|
Jamie
2014/08/13 23:21:02
No newline before 'else'.
aiguha
2014/08/15 07:09:55
Done.
| |
| 264 console.log("### No Receivers Available."); | |
| 265 } | |
| 266 }; | |
| 267 | |
| 268 /** | |
| 269 * Launches the associated receiver application by requesting that it be created | |
| 270 * or joined on the Cast device. By default, the SessionRequest passed during | |
| 271 * 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
| |
| 272 * Note: This method is intended to be used as a click listener for a custom | |
| 273 * cast button on the webpage. We currently use the default cast button in | |
| 274 * Chrome, so this method is unused. | |
| 275 */ | |
| 276 remoting.CastExtensionHandler.prototype.launchApp = function() { | |
| 277 console.log("### Launching Cast App."); | |
| 278 chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), | |
| 279 this.onLaunchError.bind(this)); | |
| 280 }; | |
| 281 | |
| 282 /** | |
| 283 * 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.
| |
| 284 * | |
| 285 * @param {chrome.cast.Session} e The requested session. | |
| 286 */ | |
| 287 remoting.CastExtensionHandler.prototype.onRequestSessionSuccess = function (e) { | |
| 288 console.log("### Successfully created session: " + e.sessionId); | |
| 289 this.session = e; | |
| 290 this.session.addUpdateListener(this.sessionUpdateListener.bind(this)); | |
| 291 if(this.session.media.length != 0) { | |
| 292 this.onMediaDiscovered('onRequestSession', this.session.media[0]); | |
| 293 } | |
| 294 this.session.addMediaListener( | |
| 295 this.onMediaDiscovered.bind(this, 'addMediaListener')); | |
| 296 this.session.addMessageListener(this.chromotingNamespace, | |
| 297 this.chromotingMessageListener.bind(this)); | |
| 298 }; | |
| 299 | |
| 300 /** | |
| 301 * Listener invoked when the launchApp() fails. | |
| 302 * @param {chrome.cast.Error} error The error code. | |
| 303 */ | |
| 304 remoting.CastExtensionHandler.prototype.onLaunchError = function(error) { | |
| 305 console.error("### Error Casting to Receiver.", error); | |
| 306 }; | |
| 307 | |
| 308 /** | |
| 309 * Stops the running receiver application associated with the session. | |
| 310 * TODO(aiguha): When the user disconnects using the blue drop down bar, | |
| 311 * the client session should notify the CastExtensionHandler, which should | |
| 312 * call this method to close the session with the Cast device. | |
| 313 */ | |
| 314 remoting.CastExtensionHandler.prototype.stopApp = function() { | |
| 315 this.session.stop(this.onStopAppSuccess.bind(this), | |
| 316 this.onStopAppError.bind(this)); | |
| 317 }; | |
| 318 | |
| 319 /** | |
| 320 * Listener invoked when the receiver application is stopped successfully. | |
| 321 */ | |
| 322 remoting.CastExtensionHandler.prototype.onStopAppSuccess = function() { | |
| 323 console.log('### Session Stopped.'); | |
| 324 }; | |
| 325 | |
| 326 /** | |
| 327 * Listener invoked when we fail to stop the receiver application. | |
| 328 * | |
| 329 * @param {chrome.cast.Error} error The error code. | |
| 330 */ | |
| 331 remoting.CastExtensionHandler.prototype.onStopAppError = function(error) { | |
| 332 console.error('### Error Stopping App: ', error); | |
| 333 }; | |
| OLD | NEW |