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