blob: 7d793b604745c82ed07c04b634137cd58ac1a743 [file] [log] [blame]
// Copyright 2015 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.
var mediaRouter;
define('media_router_bindings', [
'mojo/public/js/bindings',
'mojo/public/js/core',
'content/public/renderer/service_provider',
'chrome/browser/media/router/media_router.mojom',
'extensions/common/mojo/keep_alive.mojom',
'mojo/public/js/connection',
'mojo/public/js/router',
], function(bindings,
core,
serviceProvider,
mediaRouterMojom,
keepAliveMojom,
connector,
routerModule) {
'use strict';
/**
* Converts a media sink to a MediaSink Mojo object.
* @param {!MediaSink} sink A media sink.
* @return {!mediaRouterMojom.MediaSink} A Mojo MediaSink object.
*/
function sinkToMojo_(sink) {
return new mediaRouterMojom.MediaSink({
'name': sink.friendlyName,
'description': sink.description,
'domain': sink.domain,
'sink_id': sink.id,
'icon_type': sinkIconTypeToMojo(sink.iconType),
});
}
/**
* Converts a media sink's icon type to a MediaSink.IconType Mojo object.
* @param {!MediaSink.IconType} type A media sink's icon type.
* @return {!mediaRouterMojom.MediaSink.IconType} A Mojo MediaSink.IconType
* object.
*/
function sinkIconTypeToMojo(type) {
switch (type) {
case 'cast':
return mediaRouterMojom.MediaSink.IconType.CAST;
case 'cast_audio':
return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO;
case 'cast_audio_group':
return mediaRouterMojom.MediaSink.IconType.CAST_AUDIO_GROUP;
case 'generic':
return mediaRouterMojom.MediaSink.IconType.GENERIC;
case 'hangout':
return mediaRouterMojom.MediaSink.IconType.HANGOUT;
default:
console.error('Unknown sink icon type : ' + type);
return mediaRouterMojom.MediaSink.IconType.GENERIC;
}
}
/**
* Returns a Mojo MediaRoute object given a MediaRoute and a
* media sink name.
* @param {!MediaRoute} route
* @return {!mojo.MediaRoute}
*/
function routeToMojo_(route) {
return new mediaRouterMojom.MediaRoute({
'media_route_id': route.id,
'media_source': route.mediaSource,
'media_sink_id': route.sinkId,
'description': route.description,
'icon_url': route.iconUrl,
'is_local': route.isLocal,
'custom_controller_path': route.customControllerPath,
// TODO(imcheng): Remove logic when extension always sets the field.
'for_display': route.forDisplay == undefined ? true : route.forDisplay
});
}
/**
* Converts a route message to a RouteMessage Mojo object.
* @param {!RouteMessage} message
* @return {!mediaRouterMojom.RouteMessage} A Mojo RouteMessage object.
*/
function messageToMojo_(message) {
if ("string" == typeof message.message) {
return new mediaRouterMojom.RouteMessage({
'type': mediaRouterMojom.RouteMessage.Type.TEXT,
'message': message.message,
});
} else {
return new mediaRouterMojom.RouteMessage({
'type': mediaRouterMojom.RouteMessage.Type.BINARY,
'data': message.message,
});
}
}
/**
* Converts presentation connection state to Mojo enum value.
* @param {!string} state
* @return {!mediaRouterMojom.MediaRouter.PresentationConnectionState}
*/
function presentationConnectionStateToMojo_(state) {
var PresentationConnectionState =
mediaRouterMojom.MediaRouter.PresentationConnectionState;
switch (state) {
case 'connecting':
return PresentationConnectionState.CONNECTING;
case 'connected':
return PresentationConnectionState.CONNECTED;
case 'closed':
return PresentationConnectionState.CLOSED;
case 'terminated':
return PresentationConnectionState.TERMINATED;
default:
console.error('Unknown presentation connection state: ' + state);
return PresentationConnectionState.TERMINATED;
}
}
/**
* Creates a new MediaRouter.
* Converts a route struct to its Mojo form.
* @param {!MediaRouterService} service
* @constructor
*/
function MediaRouter(service) {
/**
* The Mojo service proxy. Allows extension code to call methods that reside
* in the browser.
* @type {!MediaRouterService}
*/
this.service_ = service;
/**
* The provider manager service delegate. Its methods are called by the
* browser-resident Mojo service.
* @type {!MediaRouter}
*/
this.mrpm_ = new MediaRouteProvider(this);
/**
* The message pipe that connects the Media Router to mrpm_ across
* browser/renderer IPC boundaries. Object must remain in scope for the
* lifetime of the connection to prevent the connection from closing
* automatically.
* @type {!mojo.MessagePipe}
*/
this.pipe_ = core.createMessagePipe();
/**
* Handle to a KeepAlive service object, which prevents the extension from
* being suspended as long as it remains in scope.
* @type {boolean}
*/
this.keepAlive_ = null;
/**
* The stub used to bind the service delegate to the Mojo interface.
* Object must remain in scope for the lifetime of the connection to
* prevent the connection from closing automatically.
* @type {!mojom.MediaRouter}
*/
this.mediaRouteProviderStub_ = connector.bindHandleToStub(
this.pipe_.handle0, mediaRouterMojom.MediaRouteProvider);
// Link mediaRouteProviderStub_ to the provider manager delegate.
bindings.StubBindings(this.mediaRouteProviderStub_).delegate = this.mrpm_;
}
/**
* Registers the Media Router Provider Manager with the Media Router.
* @return {!Promise<string>} Instance ID for the Media Router.
*/
MediaRouter.prototype.start = function() {
return this.service_.registerMediaRouteProvider(this.pipe_.handle1).then(
function(result) {
return result.instance_id;
}.bind(this));
}
/**
* Sets the service delegate methods.
* @param {Object} handlers
*/
MediaRouter.prototype.setHandlers = function(handlers) {
this.mrpm_.setHandlers(handlers);
}
/**
* The keep alive status.
* @return {boolean}
*/
MediaRouter.prototype.getKeepAlive = function() {
return this.keepAlive_ != null;
};
/**
* Called by the provider manager when a sink list for a given source is
* updated.
* @param {!string} sourceUrn
* @param {!Array<!MediaSink>} sinks
*/
MediaRouter.prototype.onSinksReceived = function(sourceUrn, sinks) {
this.service_.onSinksReceived(sourceUrn, sinks.map(sinkToMojo_));
};
/**
* Called by the provider manager to keep the extension from suspending
* if it enters a state where suspension is undesirable (e.g. there is an
* active MediaRoute.)
* If keepAlive is true, the extension is kept alive.
* If keepAlive is false, the extension is allowed to suspend.
* @param {boolean} keepAlive
*/
MediaRouter.prototype.setKeepAlive = function(keepAlive) {
if (keepAlive === false && this.keepAlive_) {
this.keepAlive_.close();
this.keepAlive_ = null;
} else if (keepAlive === true && !this.keepAlive_) {
this.keepAlive_ = new routerModule.Router(
serviceProvider.connectToService(
keepAliveMojom.KeepAlive.name));
}
};
/**
* Called by the provider manager to send an issue from a media route
* provider to the Media Router, to show the user.
* @param {!Object} issue The issue object.
*/
MediaRouter.prototype.onIssue = function(issue) {
function issueSeverityToMojo_(severity) {
switch (severity) {
case 'fatal':
return mediaRouterMojom.Issue.Severity.FATAL;
case 'warning':
return mediaRouterMojom.Issue.Severity.WARNING;
case 'notification':
return mediaRouterMojom.Issue.Severity.NOTIFICATION;
default:
console.error('Unknown issue severity: ' + severity);
return mediaRouterMojom.Issue.Severity.NOTIFICATION;
}
}
function issueActionToMojo_(action) {
switch (action) {
case 'ok':
return mediaRouterMojom.Issue.ActionType.OK;
case 'cancel':
return mediaRouterMojom.Issue.ActionType.CANCEL;
case 'dismiss':
return mediaRouterMojom.Issue.ActionType.DISMISS;
case 'learn_more':
return mediaRouterMojom.Issue.ActionType.LEARN_MORE;
default:
console.error('Unknown issue action type : ' + action);
return mediaRouterMojom.Issue.ActionType.OK;
}
}
var secondaryActions = (issue.secondaryActions || []).map(function(e) {
return issueActionToMojo_(e);
});
this.service_.onIssue(new mediaRouterMojom.Issue({
'route_id': issue.routeId,
'severity': issueSeverityToMojo_(issue.severity),
'title': issue.title,
'message': issue.message,
'default_action': issueActionToMojo_(issue.defaultAction),
'secondary_actions': secondaryActions,
'help_url': issue.helpUrl,
'is_blocking': issue.isBlocking
}));
};
/**
* Called by the provider manager when the set of active routes
* has been updated.
* @param {!Array<MediaRoute>} routes The active set of media routes.
* @param {string=} opt_sourceUrn The sourceUrn associated with this route
* query. This parameter is optional and can be empty.
* @param {Array<string>=} opt_joinableRouteIds The active set of joinable
* media routes. This parameter is optional and can be empty.
*/
MediaRouter.prototype.onRoutesUpdated =
function(routes, opt_sourceUrn, opt_joinableRouteIds) {
// TODO(boetger): This check allows backward compatibility with the Cast SDK
// and can be removed when the Cast SDK is updated.
if (typeof(opt_sourceUrn) != 'string') {
opt_sourceUrn = '';
}
this.service_.onRoutesUpdated(
routes.map(routeToMojo_),
opt_sourceUrn || '',
opt_joinableRouteIds || []);
};
/**
* Called by the provider manager when sink availability has been updated.
* @param {!mediaRouterMojom.MediaRouter.SinkAvailability} availability
* The new sink availability.
*/
MediaRouter.prototype.onSinkAvailabilityUpdated = function(availability) {
this.service_.onSinkAvailabilityUpdated(availability);
};
/**
* Called by the provider manager when the state of a presentation connected
* to a route has changed.
* @param {!string} routeId
* @param {!string} state
*/
MediaRouter.prototype.onPresentationConnectionStateChanged =
function(routeId, state) {
this.service_.onPresentationConnectionStateChanged(
routeId, presentationConnectionStateToMojo_(state));
};
/**
* Object containing callbacks set by the provider manager.
*
* @constructor
* @struct
*/
function MediaRouterHandlers() {
/**
* @type {function(!string, !string, !string, !string, !number}
*/
this.createRoute = null;
/**
* @type {function(!string, !string, !string, !number)}
*/
this.joinRoute = null;
/**
* @type {function(string)}
*/
this.terminateRoute = null;
/**
* @type {function(string)}
*/
this.startObservingMediaSinks = null;
/**
* @type {function(string)}
*/
this.stopObservingMediaSinks = null;
/**
* @type {function(string, string): Promise}
*/
this.sendRouteMessage = null;
/**
* @type {function(string, Uint8Array): Promise}
*/
this.sendRouteBinaryMessage = null;
/**
* @type {function(string):
* Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
*/
this.listenForRouteMessages = null;
/**
* @type {function(string)}
*/
this.stopListeningForRouteMessages = null;
/**
* @type {function(string)}
*/
this.detachRoute = null;
/**
* @type {function()}
*/
this.startObservingMediaRoutes = null;
/**
* @type {function()}
*/
this.stopObservingMediaRoutes = null;
/**
* @type {function()}
*/
this.connectRouteByRouteId = null;
};
/**
* Routes calls from Media Router to the provider manager extension.
* Registered with the MediaRouter stub.
* @param {!MediaRouter} MediaRouter proxy to call into the
* Media Router mojo interface.
* @constructor
*/
function MediaRouteProvider(mediaRouter) {
mediaRouterMojom.MediaRouteProvider.stubClass.call(this);
/**
* Object containing JS callbacks into Provider Manager code.
* @type {!MediaRouterHandlers}
*/
this.handlers_ = new MediaRouterHandlers();
/**
* Proxy class to the browser's Media Router Mojo service.
* @type {!MediaRouter}
*/
this.mediaRouter_ = mediaRouter;
}
MediaRouteProvider.prototype = Object.create(
mediaRouterMojom.MediaRouteProvider.stubClass.prototype);
/*
* Sets the callback handler used to invoke methods in the provider manager.
*
* @param {!MediaRouterHandlers} handlers
*/
MediaRouteProvider.prototype.setHandlers = function(handlers) {
this.handlers_ = handlers;
var requiredHandlers = [
'stopObservingMediaRoutes',
'startObservingMediaRoutes',
'sendRouteMessage',
'sendRouteBinaryMessage',
'listenForRouteMessages',
'stopListeningForRouteMessages',
'detachRoute',
'terminateRoute',
'joinRoute',
'createRoute',
'stopObservingMediaSinks',
'startObservingMediaRoutes',
'connectRouteByRouteId'
];
requiredHandlers.forEach(function(nextHandler) {
if (handlers[nextHandler] === undefined) {
console.error(nextHandler + ' handler not registered.');
}
});
}
/**
* Starts querying for sinks capable of displaying the media source
* designated by |sourceUrn|. Results are returned by calling
* OnSinksReceived.
* @param {!string} sourceUrn
*/
MediaRouteProvider.prototype.startObservingMediaSinks =
function(sourceUrn) {
this.handlers_.startObservingMediaSinks(sourceUrn);
};
/**
* Stops querying for sinks capable of displaying |sourceUrn|.
* @param {!string} sourceUrn
*/
MediaRouteProvider.prototype.stopObservingMediaSinks =
function(sourceUrn) {
this.handlers_.stopObservingMediaSinks(sourceUrn);
};
/**
* Requests that |sinkId| render the media referenced by |sourceUrn|. If the
* request is from the Presentation API, then origin and tabId will
* be populated.
* @param {!string} sourceUrn Media source to render.
* @param {!string} sinkId Media sink ID.
* @param {!string} presentationId Presentation ID from the site
* requesting presentation. TODO(mfoltz): Remove.
* @param {!string} origin Origin of site requesting presentation.
* @param {!number} tabId ID of tab requesting presentation.
* @return {!Promise.<!Object>} A Promise resolving to an object describing
* the newly created media route, or rejecting with an error message on
* failure.
*/
MediaRouteProvider.prototype.createRoute =
function(sourceUrn, sinkId, presentationId, origin, tabId) {
return this.handlers_.createRoute(
sourceUrn, sinkId, presentationId, origin, tabId)
.then(function(route) {
return {route: routeToMojo_(route)};
}.bind(this))
.catch(function(err) {
return {error_text: 'Error creating route: ' + err.message};
});
};
/**
* Handles a request via the Presentation API to join an existing route given
* by |sourceUrn| and |presentationId|. |origin| and |tabId| are used for
* validating same-origin/tab scope.
* @param {!string} sourceUrn Media source to render.
* @param {!string} presentationId Presentation ID to join.
* @param {!string} origin Origin of site requesting join.
* @param {!number} tabId ID of tab requesting join.
* @return {!Promise.<!Object>} A Promise resolving to an object describing
* the newly created media route, or rejecting with an error message on
* failure.
*/
MediaRouteProvider.prototype.joinRoute =
function(sourceUrn, presentationId, origin, tabId) {
return this.handlers_.joinRoute(sourceUrn, presentationId, origin, tabId)
.then(function(newRoute) {
return {route: routeToMojo_(newRoute)};
},
function(err) {
return {error_text: 'Error joining route: ' + err.message};
});
};
/**
* Handles a request via the Presentation API to join an existing route given
* by |sourceUrn| and |routeId|. |origin| and |tabId| are used for
* validating same-origin/tab scope.
* @param {!string} sourceUrn Media source to render.
* @param {!string} routeId Route ID to join.
* @param {!string} presentationId Presentation ID to join.
* @param {!string} origin Origin of site requesting join.
* @param {!number} tabId ID of tab requesting join.
* @return {!Promise.<!Object>} A Promise resolving to an object describing
* the newly created media route, or rejecting with an error message on
* failure.
*/
MediaRouteProvider.prototype.connectRouteByRouteId =
function(sourceUrn, routeId, presentationId, origin, tabId) {
return this.handlers_.connectRouteByRouteId(
sourceUrn, routeId, presentationId, origin, tabId)
.then(function(newRoute) {
return {route: routeToMojo_(newRoute)};
},
function(err) {
return {error_text: 'Error joining route: ' + err.message};
});
};
/**
* Terminates the route specified by |routeId|.
* @param {!string} routeId
*/
MediaRouteProvider.prototype.terminateRoute = function(routeId) {
this.handlers_.terminateRoute(routeId);
};
/**
* Posts a message to the route designated by |routeId|.
* @param {!string} routeId
* @param {!string} message
* @return {!Promise.<boolean>} Resolved with true if the message was sent,
* or false on failure.
*/
MediaRouteProvider.prototype.sendRouteMessage = function(
routeId, message) {
return this.handlers_.sendRouteMessage(routeId, message)
.then(function() {
return {'sent': true};
}, function() {
return {'sent': false};
});
};
/**
* Sends a binary message to the route designated by |routeId|.
* @param {!string} routeId
* @param {!Uint8Array} data
* @return {!Promise.<boolean>} Resolved with true if the data was sent,
* or false on failure.
*/
MediaRouteProvider.prototype.sendRouteBinaryMessage = function(
routeId, data) {
return this.handlers_.sendRouteBinaryMessage(routeId, data)
.then(function() {
return {'sent': true};
}, function() {
return {'sent': false};
});
};
/**
* Listen for next batch of messages from one of the routeIds.
* @param {!string} routeId
* @return {!Promise.<{messages: Array.<RouteMessage>, error: boolean}>}
* Resolved with a list of messages, and a boolean indicating if an error
* occurred.
*/
MediaRouteProvider.prototype.listenForRouteMessages = function(routeId) {
return this.handlers_.listenForRouteMessages(routeId)
.then(function(messages) {
return {'messages': messages.map(messageToMojo_), 'error': false};
}, function() {
return {'messages': [], 'error': true};
});
};
/**
* If there is an outstanding |listenForRouteMessages| promise for
* |routeId|, resolve that promise with an empty array.
* @param {!string} routeId
*/
MediaRouteProvider.prototype.stopListeningForRouteMessages = function(
routeId) {
return this.handlers_.stopListeningForRouteMessages(routeId);
};
/**
* Indicates that the presentation connection that was connected to |routeId|
* is no longer connected to it.
* @param {!string} routeId
*/
MediaRouteProvider.prototype.detachRoute = function(
routeId) {
this.handlers_.detachRoute(routeId);
};
/**
* Requests that the provider manager start sending information about active
* media routes to the Media Router.
* @param {!string} sourceUrn
*/
MediaRouteProvider.prototype.startObservingMediaRoutes = function(sourceUrn) {
this.handlers_.startObservingMediaRoutes(sourceUrn);
};
/**
* Requests that the provider manager stop sending information about active
* media routes to the Media Router.
* @param {!string} sourceUrn
*/
MediaRouteProvider.prototype.stopObservingMediaRoutes = function(sourceUrn) {
this.handlers_.stopObservingMediaRoutes(sourceUrn);
};
mediaRouter = new MediaRouter(connector.bindHandleToProxy(
serviceProvider.connectToService(
mediaRouterMojom.MediaRouter.name),
mediaRouterMojom.MediaRouter));
return mediaRouter;
});