Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / crd / js / cast_extension_handler.js
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 };