1 // Copyright 2015 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.
7 define('media_router_bindings', [
8 'mojo/public/js/bindings',
10 'content/public/renderer/service_provider',
11 'chrome/browser/media/router/media_router.mojom',
12 'extensions/common/mojo/keep_alive.mojom',
13 'mojo/public/js/connection',
14 'mojo/public/js/router',
25 * Converts a media sink to a MediaSink Mojo object.
26 * @param {!MediaSink} sink A media sink.
27 * @return {!mediaRouterMojom.MediaSink} A Mojo MediaSink object.
29 function sinkToMojo_(sink) {
30 return new mediaRouterMojom.MediaSink({
31 'name': sink.friendlyName,
33 'is_launching': sink.isLaunching_,
38 * Returns a Mojo MediaRoute object given a MediaRoute and a
40 * @param {!MediaRoute} route
41 * @param {!string} sinkName
42 * @return {!mojo.MediaRoute}
44 function routeToMojo_(route, sinkName) {
45 return new mediaRouterMojom.MediaRoute({
46 'media_route_id': route.id,
47 'media_source': route.mediaSource,
48 'media_sink': new mediaRouterMojom.MediaSink({
49 'sink_id': route.sinkId,
52 'description': route.description,
53 'icon_url': route.iconUrl,
54 'is_local': route.isLocal,
55 'custom_controller_path': route.customControllerPath,
60 * Converts a route message to a RouteMessage Mojo object.
61 * @param {!RouteMessage} message
62 * @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
64 function messageToMojo_(message) {
65 if ("string" == typeof message.message) {
66 return new mediaRouterMojom.RouteMessage({
67 'type': mediaRouterMojom.RouteMessage.Type.TEXT,
68 'message': message.message,
71 return new mediaRouterMojom.RouteMessage({
72 'type': mediaRouterMojom.RouteMessage.Type.BINARY,
73 'data': message.message,
79 * Creates a new MediaRouter.
80 * Converts a route struct to its Mojo form.
81 * @param {!MediaRouterService} service
84 function MediaRouter(service) {
86 * The Mojo service proxy. Allows extension code to call methods that reside
88 * @type {!MediaRouterService}
90 this.service_ = service;
93 * The provider manager service delegate. Its methods are called by the
94 * browser-resident Mojo service.
95 * @type {!MediaRouter}
97 this.mrpm_ = new MediaRouteProvider(this);
100 * The message pipe that connects the Media Router to mrpm_ across
101 * browser/renderer IPC boundaries. Object must remain in scope for the
102 * lifetime of the connection to prevent the connection from closing
104 * @type {!mojo.MessagePipe}
106 this.pipe_ = core.createMessagePipe();
109 * Handle to a KeepAlive service object, which prevents the extension from
110 * being suspended as long as it remains in scope.
113 this.keepAlive_ = null;
116 * The stub used to bind the service delegate to the Mojo interface.
117 * Object must remain in scope for the lifetime of the connection to
118 * prevent the connection from closing automatically.
119 * @type {!mojom.MediaRouter}
121 this.mediaRouteProviderStub_ = connector.bindHandleToStub(
122 this.pipe_.handle0, mediaRouterMojom.MediaRouteProvider);
124 // Link mediaRouteProviderStub_ to the provider manager delegate.
125 bindings.StubBindings(this.mediaRouteProviderStub_).delegate = this.mrpm_;
129 * Registers the Media Router Provider Manager with the Media Router.
130 * @return {!Promise<string>} Instance ID for the Media Router.
132 MediaRouter.prototype.start = function() {
133 return this.service_.registerMediaRouteProvider(this.pipe_.handle1).then(
135 return result.instance_id;
140 * Sets the service delegate methods.
141 * @param {Object} handlers
143 MediaRouter.prototype.setHandlers = function(handlers) {
144 this.mrpm_.setHandlers(handlers);
148 * The keep alive status.
151 MediaRouter.prototype.getKeepAlive = function() {
152 return this.keepAlive_ != null;
156 * Called by the provider manager when a sink list for a given source is
158 * @param {!string} sourceUrn
159 * @param {!Array<!MediaSink>} sinks
161 MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks) {
162 this.service_.onSinksReceived(sourceUrn, sinks.map(sinkToMojo_));
166 * Called by the provider manager to keep the extension from suspending
167 * if it enters a state where suspension is undesirable (e.g. there is an
168 * active MediaRoute.)
169 * If keepAlive is true, the extension is kept alive.
170 * If keepAlive is false, the extension is allowed to suspend.
171 * @param {boolean} keepAlive
173 MediaRouter.prototype.setKeepAlive = function(keepAlive) {
174 if (keepAlive === false && this.keepAlive_) {
175 this.keepAlive_.close();
176 this.keepAlive_ = null;
177 } else if (keepAlive === true && !this.keepAlive_) {
178 this.keepAlive_ = new routerModule.Router(
179 serviceProvider.connectToService(
180 keepAliveMojom.KeepAlive.name));
185 * Called by the provider manager to send an issue from a media route
186 * provider to the Media Router, to show the user.
187 * @param {!Object} issue The issue object.
189 MediaRouter.prototype.onIssue = function(issue) {
190 function issueSeverityToMojo_(severity) {
193 return mediaRouterMojom.Issue.Severity.FATAL;
195 return mediaRouterMojom.Issue.Severity.WARNING;
197 return mediaRouterMojom.Issue.Severity.NOTIFICATION;
199 console.error('Unknown issue severity: ' + severity);
200 return mediaRouterMojom.Issue.Severity.NOTIFICATION;
204 function issueActionToMojo_(action) {
207 return mediaRouterMojom.Issue.ActionType.OK;
209 return mediaRouterMojom.Issue.ActionType.CANCEL;
211 return mediaRouterMojom.Issue.ActionType.DISMISS;
213 return mediaRouterMojom.Issue.ActionType.LEARN_MORE;
215 console.error('Unknown issue action type : ' + action);
216 return mediaRouterMojom.Issue.ActionType.OK;
220 var secondaryActions = (issue.secondaryActions || []).map(function(e) {
221 return issueActionToMojo_(e);
223 this.service_.onIssue(new mediaRouterMojom.Issue({
224 'route_id': issue.routeId,
225 'severity': issueSeverityToMojo_(issue.severity),
226 'title': issue.title,
227 'message': issue.message,
228 'default_action': issueActionToMojo_(issue.defaultAction),
229 'secondary_actions': secondaryActions,
230 'help_url': issue.helpUrl,
231 'is_blocking': issue.isBlocking
236 * Called by the provider manager when the set of active routes
238 * @param {!Array<MediaRoute>} routes The active set of media routes.
239 * @param {!Array<MediaSink>} sinks The active set of media sinks.
241 MediaRouter.prototype.onRoutesUpdated = function(routes, sinks) {
242 // Create an inverted index relating sink IDs to their names.
243 var sinkNameMap = {};
244 for (var i = 0; i < sinks.length; i++) {
245 sinkNameMap[sinks[i].id] = sinks[i].friendlyName;
248 // Convert MediaRoutes to Mojo objects and add their sink names
250 var mojoRoutes = routes.map(function(nextRoute) {
251 return routeToMojo_(nextRoute, sinkNameMap[nextRoute.sinkId]);
254 this.service_.onRoutesUpdated(mojoRoutes, sinks.map(sinkToMojo_));
258 * Called by the Provider Manager when an error was encountered in response
259 * to a media route creation request.
260 * @param {!string} requestId The request id.
261 * @param {!string} error The error.
263 MediaRouter.prototype.onRouteResponseError =
264 function(requestId, error) {
265 this.service_.onRouteResponseError(requestId, error);
269 * Called by the provider manager when a route was able to be created by a
270 * media route provider.
272 * @param {string} requestId The media route request id.
273 * @param {string} routeId The id of the media route that was created.
275 MediaRouter.prototype.onRouteResponseReceived =
276 function(requestId, routeId) {
277 this.service_.onRouteResponseReceived(requestId, routeId);
281 * Object containing callbacks set by the provider manager.
282 * TODO(mfoltz): Better named ProviderManagerDelegate?
287 function MediaRouterHandlers() {
289 * @type {function(!string, !string, !string, !string, !number}
291 this.createRoute = null;
294 * @type {function(!string, !string, !string, !number)}
296 this.joinRoute = null;
299 * @type {function(string)}
301 this.closeRoute = null;
304 * @type {function(string)}
306 this.startObservingMediaSinks = null;
309 * @type {function(string)}
311 this.stopObservingMediaSinks = null;
314 * @type {function(string, string): Promise}
316 this.sendRouteMessage = null;
319 * @type {function(string, Uint8Array): Promise}
321 this.sendRouteBinaryMessage = null;
324 * @type {function(string):
325 * Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
327 this.listenForRouteMessages = null;
330 * @type {function(string)}
332 this.stopListeningForRouteMessages = null;
335 * @type {function(string)}
337 this.onPresentationSessionDetached = null;
342 this.startObservingMediaRoutes = null;
347 this.stopObservingMediaRoutes = null;
351 * Routes calls from Media Router to the provider manager extension.
352 * Registered with the MediaRouter stub.
353 * @param {!MediaRouter} MediaRouter proxy to call into the
354 * Media Router mojo interface.
357 function MediaRouteProvider(mediaRouter) {
358 mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
361 * Object containing JS callbacks into Provider Manager code.
362 * @type {!MediaRouterHandlers}
364 this.handlers_ = new MediaRouterHandlers();
367 * Proxy class to the browser's Media Router Mojo service.
368 * @type {!MediaRouter}
370 this.mediaRouter_ = mediaRouter;
372 MediaRouteProvider.prototype = Object.create(
373 mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
376 * Sets the callback handler used to invoke methods in the provider manager.
378 * TODO(mfoltz): Rename to something more explicit?
379 * @param {!MediaRouterHandlers} handlers
381 MediaRouteProvider.prototype.setHandlers = function(handlers) {
382 this.handlers_ = handlers;
383 var requiredHandlers = [
384 'stopObservingMediaRoutes',
385 'startObservingMediaRoutes',
387 'sendRouteBinaryMessage',
388 'listenForRouteMessages',
389 'stopListeningForRouteMessages',
390 'onPresentationSessionDetached',
394 'stopObservingMediaSinks',
395 'startObservingMediaRoutes'
397 requiredHandlers.forEach(function(nextHandler) {
398 if (handlers[nextHandler] === undefined) {
399 console.error(nextHandler + ' handler not registered.');
405 * Starts querying for sinks capable of displaying the media source
406 * designated by |sourceUrn|. Results are returned by calling
408 * @param {!string} sourceUrn
410 MediaRouteProvider.prototype.startObservingMediaSinks =
411 function(sourceUrn) {
412 this.handlers_.startObservingMediaSinks(sourceUrn);
416 * Stops querying for sinks capable of displaying |sourceUrn|.
417 * @param {!string} sourceUrn
419 MediaRouteProvider.prototype.stopObservingMediaSinks =
420 function(sourceUrn) {
421 this.handlers_.stopObservingMediaSinks(sourceUrn);
425 * Requests that |sinkId| render the media referenced by |sourceUrn|. If the
426 * request is from the Presentation API, then origin and tabId will
428 * @param {!string} sourceUrn Media source to render.
429 * @param {!string} sinkId Media sink ID.
430 * @param {!string} presentationId Presentation ID from the site
431 * requesting presentation. TODO(mfoltz): Remove.
432 * @param {!string} origin Origin of site requesting presentation.
433 * @param {!number} tabId ID of tab requesting presentation.
434 * @return {!Promise.<!Object>} A Promise resolving to an object describing
435 * the newly created media route, or rejecting with an error message on
438 MediaRouteProvider.prototype.createRoute =
439 function(sourceUrn, sinkId, presentationId, origin, tabId) {
440 return this.handlers_.createRoute(
441 sourceUrn, sinkId, presentationId, origin, tabId)
442 .then(function(route) {
443 // Sink name is not used, so it is omitted here.
444 return {route: routeToMojo_(route, "")};
446 .catch(function(err) {
447 return {error_text: 'Error creating route: ' + err.message};
452 * Handles a request via the Presentation API to join an existing route given
453 * by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
454 * validating same-origin/tab scope.
455 * @param {!string} sourceUrn Media source to render.
456 * @param {!string} presentationId Presentation ID to join.
457 * @param {!string} origin Origin of site requesting join.
458 * @param {!number} tabId ID of tab requesting join.
459 * @return {!Promise.<!Object>} A Promise resolving to an object describing
460 * the newly created media route, or rejecting with an error message on
463 MediaRouteProvider.prototype.joinRoute =
464 function(sourceUrn, presentationId, origin, tabId) {
465 return this.handlers_.joinRoute(sourceUrn, presentationId, origin, tabId)
466 .then(function(newRoute) {
467 // Sink name is not used, so it is omitted here.
468 return {route: routeToMojo_(newRoute, "")};
471 return {error_text: 'Error joining route: ' + err.message};
476 * Closes the route specified by |routeId|.
477 * @param {!string} routeId
479 MediaRouteProvider.prototype.closeRoute = function(routeId) {
480 this.handlers_.closeRoute(routeId);
484 * Posts a message to the route designated by |routeId|.
485 * @param {!string} routeId
486 * @param {!string} message
487 * @return {!Promise.<boolean>} Resolved with true if the message was sent,
488 * or false on failure.
490 MediaRouteProvider.prototype.sendRouteMessage = function(
492 return this.handlers_.sendRouteMessage(routeId, message)
494 return {'sent': true};
496 return {'sent': false};
501 * Sends a binary message to the route designated by |routeId|.
502 * @param {!string} routeId
503 * @param {!Uint8Array} data
504 * @return {!Promise.<boolean>} Resolved with true if the data was sent,
505 * or false on failure.
507 MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
509 return this.handlers_.sendRouteBinaryMessage(routeId, data)
511 return {'sent': true};
513 return {'sent': false};
518 * Listen for next batch of messages from one of the routeIds.
519 * @param {!string} routeId
520 * @return {!Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
521 * Resolved with a list of messages, and a boolean indicating if an error
524 MediaRouteProvider.prototype.listenForRouteMessages = function(routeId) {
525 return this.handlers_.listenForRouteMessages(routeId)
526 .then(function(messages) {
527 return {'messages': messages.map(messageToMojo_), 'error': false};
529 return {'messages': [], 'error': true};
534 * If there is an outstanding |listenForRouteMessages| promise for
535 * |routeId|, resolve that promise with an empty array.
536 * @param {!string} routeId
538 MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
540 return this.handlers_.stopListeningForRouteMessages(routeId);
544 * Indicates that the presentation session that was connected to |routeId| is
545 * no longer connected to it.
546 * @param {!string} routeId
548 MediaRouteProvider.prototype.onPresentationSessionDetached = function(
550 this.handlers_.onPresentationSessionDetached(routeId);
554 * Requests that the provider manager start sending information about active
555 * media routes to the Media Router.
557 MediaRouteProvider.prototype.startObservingMediaRoutes = function() {
558 this.handlers_.startObservingMediaRoutes();
562 * Requests that the provider manager stop sending information about active
563 * media routes to the Media Router.
565 MediaRouteProvider.prototype.stopObservingMediaRoutes = function() {
566 this.handlers_.stopObservingMediaRoutes();
569 mediaRouter = new MediaRouter(connector.bindHandleToProxy(
570 serviceProvider.connectToService(
571 mediaRouterMojom.MediaRouter.name),
572 mediaRouterMojom.MediaRouter));