Bug 901803 - Add a JS component for casting to native devices. r=mfinkle

This commit is contained in:
Wes Johnston 2014-06-24 16:52:00 -07:00
Родитель ec9cd39258
Коммит ed90150cc6
4 изменённых файлов: 157 добавлений и 13 удалений

Просмотреть файл

@ -25,6 +25,14 @@ var fireflyTarget = {
}
};
var mediaPlayerTarget = {
target: "media:router",
factory: function(aService) {
Cu.import("resource://gre/modules/MediaPlayerApp.jsm");
return new MediaPlayerApp(aService);
}
};
var CastingApps = {
_castMenuId: -1,
@ -36,6 +44,7 @@ var CastingApps = {
// Register targets
SimpleServiceDiscovery.registerTarget(rokuTarget);
SimpleServiceDiscovery.registerTarget(fireflyTarget);
SimpleServiceDiscovery.registerTarget(mediaPlayerTarget);
// Search for devices continuously every 120 seconds
SimpleServiceDiscovery.search(120 * 1000);

Просмотреть файл

@ -0,0 +1,106 @@
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["MediaPlayerApp"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
let log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "MediaPlayerApp");
// Helper function for sending commands to Java.
function send(type, data, callback) {
let msg = {
type: type
};
for (let i in data) {
msg[i] = data[i];
}
sendMessageToJava(msg, callback);
}
/* These apps represent players supported natively by the platform. This class will proxy commands
* to native controls */
function MediaPlayerApp(service) {
this.service = service;
this.location = service.location;
this.id = service.uuid;
}
MediaPlayerApp.prototype = {
start: function start(callback) {
send("MediaPlayer:Start", { id: this.id }, (result) => {
if (callback) callback(true);
});
},
stop: function stop(callback) {
send("MediaPlayer:Stop", { id: this.id }, (result) => {
if (callback) callback(true);
});
},
remoteMedia: function remoteMedia(callback, listener) {
if (callback) {
callback(new RemoteMedia(this.id, listener));
}
},
}
/* RemoteMedia provides a proxy to a native media player session.
*/
function RemoteMedia(id, listener) {
this._id = id;
this._listener = listener;
if ("onRemoteMediaStart" in this._listener) {
Services.tm.mainThread.dispatch((function() {
this._listener.onRemoteMediaStart(this);
}).bind(this), Ci.nsIThread.DISPATCH_NORMAL);
}
}
RemoteMedia.prototype = {
shutdown: function shutdown() {
this._send("MediaPlayer:End", {}, (result) => {
this._status = "shutdown";
if ("onRemoteMediaStop" in this._listener) {
this._listener.onRemoteMediaStop(this);
}
});
},
play: function play() {
this._send("MediaPlayer:Play", {}, (result) => {
this._status = "started";
});
},
pause: function pause() {
this._send("MediaPlayer:Pause", {}, (result) => {
this._status = "paused";
});
},
load: function load(aData) {
this._send("MediaPlayer:Load", aData, (result) => {
this._status = "started";
})
},
get status() {
return this._status;
},
_send: function(msg, data, callback) {
data.id = this._id;
send(msg, data, callback);
}
}

Просмотреть файл

@ -11,6 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
// Define the "log" function as a binding of the Log.d function so it specifies
// the "debug" priority and a log tag.
@ -169,6 +170,29 @@ var SimpleServiceDiscovery = {
log("failed to convert to byte array: " + e);
}
}
// We also query Java directly here for any devices that Android might support natively (i.e. Chromecast or Miracast)
this.getAndroidDevices();
},
getAndroidDevices: function() {
sendMessageToJava({ type: "MediaPlayer:Get" }, (result) => {
for (let id in result.displays) {
let display = result.displays[id];
// Convert the native data into something matching what is created in _processService()
let service = {
location: display.location,
target: "media:router",
friendlyName: display.friendlyName,
uuid: display.uuid,
manufacturer: display.manufacturer,
modelName: display.modelName
};
this._addService(service);
}
})
},
_searchFixedTargets: function _searchFixedTargets() {
@ -313,22 +337,26 @@ var SimpleServiceDiscovery = {
aService.manufacturer = doc.querySelector("manufacturer").textContent;
aService.modelName = doc.querySelector("modelName").textContent;
// Filter out services that do not match the target filter
if (!this._filterService(aService)) {
return;
}
// Only add and notify if we don't already know about this service
if (!this._services.has(aService.uuid)) {
this._services.set(aService.uuid, aService);
Services.obs.notifyObservers(null, EVENT_SERVICE_FOUND, aService.uuid);
}
// Make sure we remember this service is not stale
this._services.get(aService.uuid).lastPing = this._searchTimestamp;
this._addService(aService);
}
}).bind(this), false);
xhr.send(null);
},
_addService: function(service) {
// Filter out services that do not match the target filter
if (!this._filterService(service)) {
return;
}
// Only add and notify if we don't already know about this service
if (!this._services.has(service.uuid)) {
this._services.set(service.uuid, service);
Services.obs.notifyObservers(null, EVENT_SERVICE_FOUND, service.uuid);
}
// Make sure we remember this service is not stale
this._services.get(service.uuid).lastPing = this._searchTimestamp;
}
}

Просмотреть файл

@ -15,6 +15,7 @@ EXTRA_JS_MODULES += [
'HomeProvider.jsm',
'JNI.jsm',
'LightweightThemeConsumer.jsm',
'MediaPlayerApp.jsm',
'Messaging.jsm',
'Notifications.jsm',
'OrderedBroadcast.jsm',