Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: remoting/webapp/cast_extension_handler.js

Issue 464133003: CastExtensionHandler for Chromoting Webapp (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Minor Fixes Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698