Bug 1008436 - Support Firefly multiscreen service r=mfinkle

This commit is contained in:
Brad Lassey 2014-05-21 21:37:47 -04:00
parent 54b1be3f60
commit 016d0c25b8
3 changed files with 332 additions and 0 deletions

View File

@ -13,6 +13,18 @@ var rokuTarget = {
}
};
var fireflyTarget = {
target: "urn:dial-multiscreen-org:service:dial:1",
filters: {
server: null,
modelName: "Eureka Dongle"
},
factory: function(aService) {
Cu.import("resource://gre/modules/FireflyApp.jsm");
return new FireflyApp(aService);
}
};
var CastingApps = {
_castMenuId: -1,
@ -23,6 +35,7 @@ var CastingApps = {
// Register targets
SimpleServiceDiscovery.registerTarget(rokuTarget);
SimpleServiceDiscovery.registerTarget(fireflyTarget);
// Search for devices continuously every 120 seconds
SimpleServiceDiscovery.search(120 * 1000);

View File

@ -0,0 +1,318 @@
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
/* 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 = ["FireflyApp"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
// meta constants
const HEADER_META = ".meta";
const PROTO_CMD_KEY = "cmd";
const PROTO_CMD_VALUE_CREATE_SESSION = "create-session";
const PROTO_CMD_VALUE_END_SESSION = "end-session";
const PROTO_CMD_VALUE_ATTACH_APP = "attach-app";
const PROTO_CMD_VALUE_SET_WIFI = "set-wifi";
const PROTO_CMD_RESPONSE_PREFIX = "~";
const PROTO_CMD_VALUE_START_APP = "start-app";
const PROTO_CMD_VALUE_CANCEL_START_APP = "cancel-start-app";
const PROTO_CMD_VALUE_DOWNLODING_APP = "downloading-app";
const PROTO_CMD_VALUE_ATTACH_APP_MANAGER = "attach-app-manager";
const PROTO_CMD_VALUE_EXCEPTION = "exception";
const PROTO_EXCEPTION_KEY = "exception";
const PROTO_EXCEPTION_NO_KEY = "errno";
const PROTO_STATUS_VALUE_APP_CLOSED = "app-closed";
const PROTO_STATUS_VALUE_TRY_LATER = "try-later";
const PROTO_STATUS_VALUE_CANCELLED = "cancelled";
const PROTO_STATUS_VALUE_REFUSED = "refused";
const PROTO_STATUS_VALUE_TIMEOUT = "time-out";
const PROTO_STATUS_VALUE_INVALID_APP = "app_not_exist";
const PROTO_STATUS_VALUE_APP_CHECK_FAIL = "app_check_fail";
const PROTO_STATUS_VALUE_APP_INSTALL_FAIL = "app_install_fail";
const PROTO_STATUS_VALUE_APP_DOWNLOAD_FAIL = "app_download_fail";
const PROTO_STATUS_VALUE_APP_LAUNCH_FAIL = "app_launch_fail";
const PROTO_STATUS_KEY = "status";
const PROTO_STATUS_VALUE_OK = "OK";
const PROTO_STATUS_VALUE_FAILED = "FAILED_REASON";
const PROTO_EXCEPTION_NO_APP_NAME_NULL = 10;
const PROTO_EXCEPTION_NO_MESSAGE_NULL = 11;
const PROTO_EXCEPTION_NO_NEW_GROUP_START = 12;
const PROTO_EXCEPTION_NO_UNKNOW_CMD = 13;
const PROTO_EXCEPTION_NO_JSON_DATA_ERROR = 14;
const PARAM_APP_NAME = "app-name";
const PROTO_HOME_APP = "home";
const PARAM_DOWNLOADING_PERCENT = "download-percent";
const PARAM_KEEP_SESSION = "keep-session";
const PROTO_SENDER_HOST_NAME_KEY = "sender-host-name";
const PROTO_SENDER_PORT_KEY = "sender-port";
const PROTO_APP_META_KEY = "app-meta";
const PROTO_APP_META_NAME = "name";
const PROTO_APP_META_TITLE = "title";
const PROTO_APP_META_IMG_URI = "img-uri";
const PROTO_MIME_DATA_KEY = "mime-data";
const PROTO_APP_NAME_KEY = "app-name";
const PARAM_WIFI_NAME = "wifi-name";
const PARAM_WIFI_PASSWORD = "wifi-password";
const PARAM_WIFI_TYPE = "wifi-type";
const PARAM_WIFI_KEY = "key";
const PROTO_MIME_DATA_KEY_TYPE = "type";
const PROTO_MIME_DATA_KEY_DATA = "data";
// RAMP constants
const RAMP_CMD_KEY_ID = "cmd_id";
const RAMP_CMD_KEY_TYPE = "type";
const RAMP_CMD_KEY_STATUS = "status";
const RAMP_CMD_KEY_URL = "url";
const RAMP_CMD_KEY_VALUE = "value";
const RAMP_CMD_KEY_VIDEO_NAME = "videoname";
const RAMP_CMD_KEY_EVENT_SEQ = "event_sequence";
const NAMESPACE_RAMP = "ramp";
const RAMP_CMD_ID_START = 0;
const RAMP_CMD_ID_INFO = 1;
const RAMP_CMD_ID_PLAY = 2;
const RAMP_CMD_ID_PAUSE = 3;
const RAMP_CMD_ID_STOP = 4;
const RAMP_CMD_ID_SEEKTO = 5;
const RAMP_CMD_ID_SETVOLUME = 6;
const RAMP_CMD_ID_MUTE = 7;
const RAMP_CMD_ID_DECONSTE = 8;
const RAMP_CMD_START = "START";
const RAMP_CMD_INFO = "INFO";
const RAMP_CMD_PLAY = "PLAY";
const RAMP_CMD_PAUSE = "PAUSE";
const RAMP_CMD_STOP = "STOP";
const RAMP_CMD_SEEKTO = "SEEKTO";
const RAMP_CMD_SETVOLUME = "SETVOLUME";
const RAMP_CMD_MUTE = "MUTE";
const RAMP_CMD_DECONSTE = "DECONSTE";
const RAMP_CAST_STATUS_EVENT_SEQUENCE = "event_sequence";
const RAMP_CAST_STATUS_STATE = "state";
const RAMP_CAST_STATUS_CONTENT_ID = "content_id";
const RAMP_CAST_STATUS_CURRENT_TIME = "current_time";
const RAMP_CAST_STATUS_DURATION = "duration";
const RAMP_CAST_STATUS_MUTED = "muted";
const RAMP_CAST_STATUS_TIME_PROGRESS = "time_progress";
const RAMP_CAST_STATUS_TITLE = "title";
const RAMP_CAST_STATUS_VOLUME = "volume";
const PLAYER_STATUS_PREPARING = 1;
const PLAYER_STATUS_PLAYING = 2;
const PLAYER_STATUS_PAUSE = 3;
const PLAYER_STATUS_STOP = 4;
const PLAYER_STATUS_IDLE = 5;
const PLAYER_STATUS_BUFFERING = 6;
function FireflyApp(service) {
let uri = Services.io.newURI(service.location, null, null);
this._ip = uri.host;
};
FireflyApp.prototype = {
_ip: null,
_port: 8888,
_cmd_socket: null,
_meta_callback: null,
_ramp_callbacks: {},
_mediaListener: null,
_event_sequence: 0,
status: "unloaded",
_have_session: false,
_info_timer: null,
_send_meta_cmd: function(cmd, callback, extras) {
this._meta_callback = callback;
let msg = extras ? extras : {};
msg.cmd = cmd;
this._send_cmd(JSON.stringify([HEADER_META, msg]), 0);
},
_send_ramp_cmd: function(type, cmd_id, callback, extras) {
let msg = extras ? extras : {};
msg.cmd_id = cmd_id;
msg.type = type;
msg.event_sequence = this._event_sequence++;
this._send_cmd(JSON.stringify([NAMESPACE_RAMP, msg]), 0);
},
_send_cmd: function(str, recursionDepth) {
if (!this._cmd_socket) {
let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
this._cmd_socket = baseSocket.open(this._ip, 8888, { useSecureTransport: false, binaryType: "string" });
if (!(this._cmd_socket)) {
dump("socket is null");
return;
}
this._cmd_socket.ondata = function(response) {
try {
let data = JSON.parse(response.data);
let res = data[1];
switch (data[0]) {
case ".meta":
this._handle_meta_response(data[1]);
return;
case "ramp":
this._handle_ramp_response(data[1]);
return;
default:
dump("unknown response");
}
} catch(ex) {
dump("error handling response: " + ex);
if (!this._info_timer) {
this._info_timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._info_timer.init(this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
}
}
}.bind(this);
this._cmd_socket.onerror = function(err) {
this.shutdown();
Cu.reportError("error: " + err.data.name);
this._cmd_socket = null;
}.bind(this);
this._cmd_socket.onclose = function() {
this.shutdown();
this._cmd_socket = null;
Cu.reportError("closed tcp socket")
}.bind(this);
this._cmd_socket.onopen = function() {
if (recursionDepth <= 2) {
this._send_cmd(str, ++recursionDepth);
}
}.bind(this);
} else {
try {
this._cmd_socket.send(str, str.length);
let data = JSON.parse(str);
if (data[1][PARAM_APP_NAME] == PROTO_HOME_APP) {
// assuming we got home OK
if (this._meta_callback) {
this._handle_meta_callback({ status: "OK" });
}
}
} catch (ex) {
this._cmd_socket = null;
this._send_cmd(str);
}
}
},
observe: function(subject, data, topic) {
if (data === "timer-callback") {
this._info_timer = null;
this._send_ramp_cmd(RAMP_CMD_INFO, RAMP_CMD_ID_INFO, null)
}
},
start: function(func) {
let cmd = this._have_session ? PROTO_CMD_VALUE_START_APP : PROTO_CMD_VALUE_CREATE_SESSION ;
this._send_meta_cmd(cmd, func, { "app-name": "Remote Player" });
},
stop: function(func) {
if (func) {
func(true);
}
},
remoteMedia: function(func, listener) {
this._mediaListener = listener;
func(this);
if (listener) {
listener.onRemoteMediaStart(this);
}
},
_handle_meta_response: function(data) {
switch(data.cmd) {
case "create-session":
case "~create-session":
// if we get a response form start-app, assume we have a connection already
case "start-app":
case "~start-app":
this._have_session = (data.status == "OK");
break;
case "end-session":
case "~end-session":
this._have_session = (data.status != "OK");
break;
}
if (this._meta_callback) {
let callback = this._meta_callback;
this._meta_callback = null;
callback(data.status == "OK");
}
},
_handle_ramp_response: function(data) {
switch (data.status.state) {
case PLAYER_STATUS_PREPARING:
this.status = "preparing";
break;
case PLAYER_STATUS_PLAYING:
this.status = "started";
break;
case PLAYER_STATUS_PAUSE:
this.status = "paused";
break;
case PLAYER_STATUS_STOP:
this.status = "stopped";
break;
case PLAYER_STATUS_IDLE:
this.status = "idle";
break;
case PLAYER_STATUS_BUFFERING:
this.status = "buffering";
break;
}
if (data.status.state == PLAYER_STATUS_STOP && data.status.current_time > 0 && data.status.current_time == data.status.duration) {
this.status = "completed";
} else if (!this._info_timer) {
this._info_timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._info_timer.init(this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
}
if (this._mediaListener) {
this._mediaListener.onRemoteMediaStatus(this);
}
},
load: function(data) {
let meta = {
url: data.source,
videoname: data.title
};
this._send_ramp_cmd(RAMP_CMD_START, RAMP_CMD_ID_START, null, meta);
},
play: function() {
this._send_ramp_cmd(RAMP_CMD_PLAY, RAMP_CMD_ID_PLAY, null);
},
pause: function() {
this._send_ramp_cmd(RAMP_CMD_PAUSE, RAMP_CMD_ID_PAUSE, null);
},
shutdown: function() {
if (this._info_timer) {
this._info_timer.clear();
this._info_timer = null;
}
this.stop(function() {
this._send_meta_cmd(PROTO_CMD_VALUE_END_SESSION);
if (this._mediaListener) {
this._mediaListener.onRemoteMediaStop(this);
}
}.bind(this));
}
};

View File

@ -9,6 +9,7 @@ EXTRA_JS_MODULES += [
'AndroidLog.jsm',
'ContactService.jsm',
'dbg-browser-actors.js',
'FireflyApp.jsm',
'HelperApps.jsm',
'Home.jsm',
'HomeProvider.jsm',