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