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 |
| 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) { |
| 29 /** @private */ |
| 30 this.clientSession_ = clientSession; |
| 31 |
| 32 /** @type {chrome.cast.Session} @private */ |
| 33 this.session_ = null; |
| 34 |
| 35 /** @type {string} @private */ |
| 36 this.kCastNamespace_ = 'urn:x-cast:com.chromoting.cast.all'; |
| 37 |
| 38 /** @type {string} @private */ |
| 39 this.kApplicationId_ = "8A1211E3"; |
| 40 |
| 41 /** @type {Array.<Object>} @private */ |
| 42 this.messageQueue_ = []; |
| 43 |
| 44 this.start_(); |
| 45 }; |
| 46 |
| 47 /** |
| 48 * The id of the script node. |
| 49 * @type {string} |
| 50 * @private |
| 51 */ |
| 52 remoting.CastExtensionHandler.prototype.SCRIPT_NODE_ID_ = 'cast-script-node'; |
| 53 |
| 54 /** |
| 55 * Attempts to load the Google Cast Chrome Sender API libary. |
| 56 * @private |
| 57 */ |
| 58 remoting.CastExtensionHandler.prototype.start_ = function() { |
| 59 var node = document.getElementById(this.SCRIPT_NODE_ID_); |
| 60 if (node) { |
| 61 console.error( |
| 62 'Multiple calls to CastExtensionHandler.start_ not expected.'); |
| 63 return; |
| 64 } |
| 65 |
| 66 // Create a script node to load the Cast Sender API. |
| 67 node = document.createElement('script'); |
| 68 node.id = this.SCRIPT_NODE_ID_; |
| 69 node.src = "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"; |
| 70 node.type = 'text/javascript'; |
| 71 document.body.insertBefore(node, document.body.firstChild); |
| 72 |
| 73 /** @type {remoting.CastExtensionHandler} */ |
| 74 var that = this; |
| 75 var onLoad = function() { |
| 76 window['__onGCastApiAvailable'] = that.onGCastApiAvailable.bind(that); |
| 77 |
| 78 }; |
| 79 var onLoadError = function(event) { |
| 80 console.error("Failed to load Chrome Cast Sender API."); |
| 81 } |
| 82 node.addEventListener('load', onLoad, false); |
| 83 node.addEventListener('error', onLoadError, false); |
| 84 |
| 85 }; |
| 86 |
| 87 /** |
| 88 * Process Cast Extension Messages from the Chromoting host. |
| 89 * @param {string} msgString The extension message's data. |
| 90 */ |
| 91 remoting.CastExtensionHandler.prototype.onMessage = function(msgString) { |
| 92 var message = getJsonObjectFromString(msgString); |
| 93 |
| 94 // Save messages to send after a session is established. |
| 95 this.messageQueue_.push(message); |
| 96 // Trigger the sending of pending messages, followed by the one just |
| 97 // received. |
| 98 if (this.session_) { |
| 99 this.sendPendingMessages_(); |
| 100 } |
| 101 }; |
| 102 |
| 103 /** |
| 104 * Send cast-extension messages through the client session. |
| 105 * @param {Object} response The JSON response to be sent to the host. The |
| 106 * response object must contain the appropriate keys. |
| 107 * @private |
| 108 */ |
| 109 remoting.CastExtensionHandler.prototype.sendMessageToHost_ = |
| 110 function(response) { |
| 111 this.clientSession_.sendCastExtensionMessage(response); |
| 112 }; |
| 113 |
| 114 /** |
| 115 * Send pending messages from the host to the receiver app. |
| 116 * @private |
| 117 */ |
| 118 remoting.CastExtensionHandler.prototype.sendPendingMessages_ = function() { |
| 119 var len = this.messageQueue_.length; |
| 120 for(var i = 0; i<len; i++) { |
| 121 this.session_.sendMessage(this.kCastNamespace_, |
| 122 this.messageQueue_[i], |
| 123 this.sendMessageSuccess.bind(this), |
| 124 this.sendMessageFailure.bind(this)); |
| 125 } |
| 126 this.messageQueue_ = []; |
| 127 }; |
| 128 |
| 129 /** |
| 130 * Event handler for '__onGCastApiAvailable' window event. This event is |
| 131 * triggered if the Google Cast Chrome Sender API is available. We attempt to |
| 132 * load this API in this.start(). If the API loaded successfully, we can proceed |
| 133 * to initialize it and configure it to launch our Cast Receiver Application. |
| 134 * |
| 135 * @param {boolean} loaded True if the API loaded succesfully. |
| 136 * @param {Object} errorInfo Info if the API load failed. |
| 137 */ |
| 138 remoting.CastExtensionHandler.prototype.onGCastApiAvailable = |
| 139 function(loaded, errorInfo) { |
| 140 if (loaded) { |
| 141 this.initializeCastApi(); |
| 142 } else { |
| 143 console.log(errorInfo); |
| 144 } |
| 145 }; |
| 146 |
| 147 /** |
| 148 * Initialize the Cast API. |
| 149 * @private |
| 150 */ |
| 151 remoting.CastExtensionHandler.prototype.initializeCastApi = function() { |
| 152 var sessionRequest = new chrome.cast.SessionRequest(this.kApplicationId_); |
| 153 var apiConfig = |
| 154 new chrome.cast.ApiConfig(sessionRequest, |
| 155 this.sessionListener.bind(this), |
| 156 this.receiverListener.bind(this), |
| 157 chrome.cast.AutoJoinPolicy.PAGE_SCOPED, |
| 158 chrome.cast.DefaultActionPolicy.CREATE_SESSION); |
| 159 chrome.cast.initialize( |
| 160 apiConfig, this.onInitSuccess.bind(this), this.onInitError.bind(this)); |
| 161 }; |
| 162 |
| 163 /** |
| 164 * Callback for successful initialization of the Cast API. |
| 165 */ |
| 166 remoting.CastExtensionHandler.prototype.onInitSuccess = function() { |
| 167 console.log("Initialization Successful."); |
| 168 }; |
| 169 |
| 170 /** |
| 171 * Callback for failed initialization of the Cast API. |
| 172 */ |
| 173 remoting.CastExtensionHandler.prototype.onInitError = function() { |
| 174 console.error("Initialization Failed."); |
| 175 }; |
| 176 |
| 177 /** |
| 178 * Listener invoked when a session is created or connected by the SDK. |
| 179 * Note: The requestSession method would not cause this callback to be invoked |
| 180 * since it is passed its own listener. |
| 181 * @param {chrome.cast.Session} session The resulting session. |
| 182 */ |
| 183 remoting.CastExtensionHandler.prototype.sessionListener = function(session) { |
| 184 console.log('New Session:' + /** @type {string} */ (session.sessionId)); |
| 185 this.session_ = session; |
| 186 if (this.session_.media.length != 0) { |
| 187 |
| 188 // There should be no media associated with the session, since we never |
| 189 // directly load media from the Sender application. |
| 190 this.onMediaDiscovered('sessionListener', this.session_.media[0]); |
| 191 } |
| 192 this.session_.addMediaListener( |
| 193 this.onMediaDiscovered.bind(this, 'addMediaListener')); |
| 194 this.session_.addUpdateListener(this.sessionUpdateListener.bind(this)); |
| 195 this.session_.addMessageListener(this.kCastNamespace_, |
| 196 this.chromotingMessageListener.bind(this)); |
| 197 this.session_.sendMessage(this.kCastNamespace_, |
| 198 {subject : 'test', chromoting_data : 'Hello, Cast.'}, |
| 199 this.sendMessageSuccess.bind(this), |
| 200 this.sendMessageFailure.bind(this)); |
| 201 this.sendPendingMessages_(); |
| 202 }; |
| 203 |
| 204 /** |
| 205 * Listener invoked when a media session is created by another sender. |
| 206 * @param {string} how How this callback was triggered. |
| 207 * @param {chrome.cast.media.Media} media The media item discovered. |
| 208 * @private |
| 209 */ |
| 210 remoting.CastExtensionHandler.prototype.onMediaDiscovered = |
| 211 function(how, media) { |
| 212 console.error("Unexpected media session discovered."); |
| 213 }; |
| 214 |
| 215 /** |
| 216 * Listener invoked when a cast extension message was sent to the cast device |
| 217 * successfully. |
| 218 * @private |
| 219 */ |
| 220 remoting.CastExtensionHandler.prototype.sendMessageSuccess = function() { |
| 221 }; |
| 222 |
| 223 /** |
| 224 * Listener invoked when a cast extension message failed to be sent to the cast |
| 225 * device. |
| 226 * @param {Object} error The error. |
| 227 * @private |
| 228 */ |
| 229 remoting.CastExtensionHandler.prototype.sendMessageFailure = function(error) { |
| 230 console.error('Failed to Send Message.', error); |
| 231 }; |
| 232 |
| 233 /** |
| 234 * Listener invoked when a cast extension message is received from the Cast |
| 235 * device. |
| 236 * @param {string} ns The namespace of the message received. |
| 237 * @param {string} message The stringified JSON message received. |
| 238 */ |
| 239 remoting.CastExtensionHandler.prototype.chromotingMessageListener = |
| 240 function(ns, message) { |
| 241 if (ns === this.kCastNamespace_) { |
| 242 try { |
| 243 var messageObj = getJsonObjectFromString(message); |
| 244 this.sendMessageToHost_(messageObj); |
| 245 } catch (err) { |
| 246 console.error('Failed to process message from Cast device.'); |
| 247 } |
| 248 } else { |
| 249 console.error("Unexpected message from Cast device."); |
| 250 } |
| 251 }; |
| 252 |
| 253 /** |
| 254 * Listener invoked when there updates to the current session. |
| 255 * |
| 256 * @param {boolean} isAlive True if the session is still alive. |
| 257 */ |
| 258 remoting.CastExtensionHandler.prototype.sessionUpdateListener = |
| 259 function(isAlive) { |
| 260 var message = isAlive ? 'Session Updated' : 'Session Removed'; |
| 261 message += ': ' + this.session_.sessionId +'.'; |
| 262 console.log(message); |
| 263 }; |
| 264 |
| 265 /** |
| 266 * Listener invoked when the availability of a Cast receiver that supports |
| 267 * the application in sessionRequest is known or changes. |
| 268 * |
| 269 * @param {chrome.cast.ReceiverAvailability} availability Receiver availability. |
| 270 */ |
| 271 remoting.CastExtensionHandler.prototype.receiverListener = |
| 272 function(availability) { |
| 273 if (availability === chrome.cast.ReceiverAvailability.AVAILABLE) { |
| 274 console.log("Receiver(s) Found."); |
| 275 } else { |
| 276 console.error("No Receivers Available."); |
| 277 } |
| 278 }; |
| 279 |
| 280 /** |
| 281 * Launches the associated receiver application by requesting that it be created |
| 282 * on the Cast device. It uses the SessionRequest passed during initialization |
| 283 * to determine what application to launch on the Cast device. |
| 284 * |
| 285 * Note: This method is intended to be used as a click listener for a custom |
| 286 * cast button on the webpage. We currently use the default cast button in |
| 287 * Chrome, so this method is unused. |
| 288 */ |
| 289 remoting.CastExtensionHandler.prototype.launchApp = function() { |
| 290 chrome.cast.requestSession(this.onRequestSessionSuccess.bind(this), |
| 291 this.onLaunchError.bind(this)); |
| 292 }; |
| 293 |
| 294 /** |
| 295 * Listener invoked when chrome.cast.requestSession completes successfully. |
| 296 * |
| 297 * @param {chrome.cast.Session} session The requested session. |
| 298 */ |
| 299 remoting.CastExtensionHandler.prototype.onRequestSessionSuccess = |
| 300 function (session) { |
| 301 this.session_ = session; |
| 302 this.session_.addUpdateListener(this.sessionUpdateListener.bind(this)); |
| 303 if (this.session_.media.length != 0) { |
| 304 this.onMediaDiscovered('onRequestSession', this.session_.media[0]); |
| 305 } |
| 306 this.session_.addMediaListener( |
| 307 this.onMediaDiscovered.bind(this, 'addMediaListener')); |
| 308 this.session_.addMessageListener(this.kCastNamespace_, |
| 309 this.chromotingMessageListener.bind(this)); |
| 310 }; |
| 311 |
| 312 /** |
| 313 * Listener invoked when chrome.cast.requestSession fails. |
| 314 * @param {chrome.cast.Error} error The error code. |
| 315 */ |
| 316 remoting.CastExtensionHandler.prototype.onLaunchError = function(error) { |
| 317 console.error("Error Casting to Receiver.", error); |
| 318 }; |
| 319 |
| 320 /** |
| 321 * Stops the running receiver application associated with the session. |
| 322 * TODO(aiguha): When the user disconnects using the blue drop down bar, |
| 323 * the client session should notify the CastExtensionHandler, which should |
| 324 * call this method to close the session with the Cast device. |
| 325 */ |
| 326 remoting.CastExtensionHandler.prototype.stopApp = function() { |
| 327 this.session_.stop(this.onStopAppSuccess.bind(this), |
| 328 this.onStopAppError.bind(this)); |
| 329 }; |
| 330 |
| 331 /** |
| 332 * Listener invoked when the receiver application is stopped successfully. |
| 333 */ |
| 334 remoting.CastExtensionHandler.prototype.onStopAppSuccess = function() { |
| 335 }; |
| 336 |
| 337 /** |
| 338 * Listener invoked when we fail to stop the receiver application. |
| 339 * |
| 340 * @param {chrome.cast.Error} error The error code. |
| 341 */ |
| 342 remoting.CastExtensionHandler.prototype.onStopAppError = function(error) { |
| 343 console.error('Error Stopping App: ', error); |
| 344 }; |
OLD | NEW |