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 |