package computician.janusclientapi;
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import java.math.BigInteger;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import android.opengl.EGLContext;
import android.os.AsyncTask;
/**
* Created by ben.trent on 5/7/2015.
*/
public class JanusServer implements Runnable, IJanusMessageObserver, IJanusSessionCreationCallbacks, IJanusAttachPluginCallbacks {
private class RandomString {
final String str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
final Random rnd = new Random();
public String randomString(Integer length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(str.charAt(rnd.nextInt(str.length())));
}
return sb.toString();
}
}
private final RandomString stringGenerator = new RandomString();
private ConcurrentHashMap<BigInteger, JanusPluginHandle> attachedPlugins = new ConcurrentHashMap<BigInteger, JanusPluginHandle>();
private Object attachedPluginsLock = new Object();
private ConcurrentHashMap<String, ITransactionCallbacks> transactions = new ConcurrentHashMap<String, ITransactionCallbacks>();
private Object transactionsLock = new Object();
public final String serverUri;
public final IJanusGatewayCallbacks gatewayObserver;
public final List<PeerConnection.IceServer> iceServers;
public final Boolean ipv6Support;
public final Integer maxPollEvents;
private BigInteger sessionId;
private Boolean connected;
private final IJanusMessenger serverConnection;
private volatile Thread keep_alive;
private Boolean peerConnectionFactoryInitialized = false;
private class AsyncAttach extends AsyncTask<IJanusPluginCallbacks, Void ,Void>{
protected Void doInBackground(IJanusPluginCallbacks... cbs){
IJanusPluginCallbacks cb = cbs[0];
try {
JSONObject obj = new JSONObject();
obj.put("janus", JanusMessageType.attach);
obj.put("plugin", cb.getPlugin());
if (serverConnection.getMessengerType() == JanusMessengerType.websocket)
obj.put("session_id", sessionId);
ITransactionCallbacks tcb = JanusTransactionCallbackFactory.createNewTransactionCallback(JanusServer.this, TransactionType.attach, cb.getPlugin(), cb);
String transaction = putNewTransaction(tcb);
obj.put("transaction", transaction);
serverConnection.sendMessage(obj.toString(), sessionId);
} catch (JSONException ex) {
onCallbackError(ex.getMessage());
}
return null;
};
}
public JanusServer(IJanusGatewayCallbacks gatewayCallbacks) {
gatewayObserver = gatewayCallbacks;
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
serverUri = gatewayObserver.getServerUri();
iceServers = gatewayObserver.getIceServers();
ipv6Support = gatewayObserver.getIpv6Support();
maxPollEvents = gatewayObserver.getMaxPollEvents();
connected = false;
sessionId = new BigInteger("-1");
serverConnection = JanusMessagerFactory.createMessager(serverUri, this);
}
private String putNewTransaction(ITransactionCallbacks transactionCallbacks) {
String transaction = stringGenerator.randomString(12);
synchronized (transactionsLock) {
while (transactions.containsKey(transaction))
transaction = stringGenerator.randomString(12);
transactions.put(transaction, transactionCallbacks);
}
return transaction;
}
private void createSession() {
try {
JSONObject obj = new JSONObject();
obj.put("janus", JanusMessageType.create);
ITransactionCallbacks cb = JanusTransactionCallbackFactory.createNewTransactionCallback(this, TransactionType.create);
String transaction = putNewTransaction(cb);
obj.put("transaction", transaction);
serverConnection.sendMessage(obj.toString());
} catch (JSONException ex) {
onCallbackError(ex.getMessage());
}
}
public boolean initializeMediaContext(Context context, boolean audio, boolean video, boolean videoHwAcceleration, EGLContext eglContext) {
if (!PeerConnectionFactory.initializeAndroidGlobals(context, audio, video, videoHwAcceleration, eglContext))
return false;
peerConnectionFactoryInitialized = true;
return true;
}
public void run() {
Thread thisThread = Thread.currentThread();
while (keep_alive == thisThread) {
try {
thisThread.sleep(25000);
} catch (InterruptedException ex) {
}
if (!connected || serverConnection.getMessengerType() != JanusMessengerType.websocket)
return;
JSONObject obj = new JSONObject();
try {
obj.put("janus", JanusMessageType.keepalive.toString());
if (serverConnection.getMessengerType() == JanusMessengerType.websocket)
obj.put("session_id", sessionId);
obj.put("transaction", stringGenerator.randomString(12));
serverConnection.sendMessage(obj.toString(), sessionId);
} catch (JSONException ex) {
gatewayObserver.onCallbackError("Keep alive failed is Janus online?" + ex.getMessage());
connected = false;
return;
}
}
}
public Boolean isConnected() {
return connected;
}
public BigInteger getSessionId() {
return sessionId;
}
public void Attach(IJanusPluginCallbacks callbacks) {
if (!peerConnectionFactoryInitialized) {
callbacks.onCallbackError("Peerconnection factory is not initialized, please initialize via initializeMediaContext so that peerconnections can be made by the plugins");
return;
}
new AsyncAttach().execute(callbacks);
}
public void Destroy() {
serverConnection.disconnect();
keep_alive = null;
connected = false;
gatewayObserver.onDestroy();
for (ConcurrentHashMap.Entry<BigInteger, JanusPluginHandle> handle : attachedPlugins.entrySet()) {
handle.getValue().detach();
}
synchronized (transactionsLock) {
for (Object trans : transactions.entrySet())
transactions.remove(trans);
}
}
public void Connect() {
serverConnection.connect();
}
public void newMessageForPlugin(String message, BigInteger plugin_id) {
JanusPluginHandle handle = null;
synchronized (attachedPluginsLock) {
handle = attachedPlugins.get(plugin_id);
}
if (handle != null) {
handle.onMessage(message);
}
}
@Override
public void onCallbackError(String msg) {
gatewayObserver.onCallbackError(msg);
}
public void sendMessage(JSONObject msg, JanusMessageType type, BigInteger handle) {
try {
msg.put("janus", type.toString());
if (serverConnection.getMessengerType() == JanusMessengerType.websocket) {
msg.put("session_id", sessionId);
msg.put("handle_id", handle);
}
msg.put("transaction", stringGenerator.randomString(12));
if (connected)
serverConnection.sendMessage(msg.toString(), sessionId, handle);
if (type == JanusMessageType.detach) {
synchronized (attachedPluginsLock) {
if (attachedPlugins.containsKey(handle))
attachedPlugins.remove(handle);
}
}
} catch (JSONException ex) {
gatewayObserver.onCallbackError(ex.getMessage());
}
}
//TODO not sure if the send message functions should be Asynchronous
public void sendMessage(TransactionType type, BigInteger handle, IPluginHandleSendMessageCallbacks callbacks, JanusSupportedPluginPackages plugin) {
JSONObject msg = callbacks.getMessage();
if (msg != null) {
try {
JSONObject newMessage = new JSONObject();
newMessage.put("janus", JanusMessageType.message.toString());
if (serverConnection.getMessengerType() == JanusMessengerType.websocket) {
newMessage.put("session_id", sessionId);
newMessage.put("handle_id", handle);
}
ITransactionCallbacks cb = JanusTransactionCallbackFactory.createNewTransactionCallback(this, TransactionType.plugin_handle_message, plugin, callbacks);
String transaction = putNewTransaction(cb);
newMessage.put("transaction", transaction);
if (msg.has("message"))
newMessage.put("body", msg.getJSONObject("message"));
if (msg.has("jsep"))
newMessage.put("jsep", msg.getJSONObject("jsep"));
serverConnection.sendMessage(newMessage.toString(), sessionId, handle);
} catch (JSONException ex) {
callbacks.onCallbackError(ex.getMessage());
}
}
}
public void sendMessage(TransactionType type, BigInteger handle, IPluginHandleWebRTCCallbacks callbacks, JanusSupportedPluginPackages plugin) {
try {
JSONObject msg = new JSONObject();
msg.put("janus", JanusMessageType.message.toString());
if (serverConnection.getMessengerType() == JanusMessengerType.websocket) {
msg.put("session_id", sessionId);
msg.put("handle_id", handle);
}
ITransactionCallbacks cb = JanusTransactionCallbackFactory.createNewTransactionCallback(this, TransactionType.plugin_handle_webrtc_message, plugin, callbacks);
String transaction = putNewTransaction(cb);
msg.put("transaction", transaction);
if (callbacks.getJsep() != null) {
msg.put("jsep", callbacks.getJsep());
}
serverConnection.sendMessage(msg.toString(), sessionId, handle);
} catch (JSONException ex) {
callbacks.onCallbackError(ex.getMessage());
}
}
//region MessageObserver
@Override
public void receivedNewMessage(JSONObject obj) {
try {
JanusMessageType type = JanusMessageType.fromString(obj.getString("janus"));
String transaction = null;
BigInteger sender = null;
if (obj.has("transaction")) {
transaction = obj.getString("transaction");
}
if (obj.has("sender"))
sender = new BigInteger(obj.getString("sender"));
JanusPluginHandle handle = null;
if (sender != null) {
synchronized (attachedPluginsLock) {
handle = attachedPlugins.get(sender);
}
}
switch (type) {
case keepalive:
break;
case ack:
case success:
case error: {
if (transaction != null) {
ITransactionCallbacks cb = null;
synchronized (transactionsLock) {
cb = transactions.get(transaction);
if (cb != null)
transactions.remove(transaction);
}
if (cb != null) {
cb.reportSuccess(obj);
transactions.remove(transaction);
}
}
break;
}
case hangup: {
if(handle != null) {
handle.hangUp();
}
break;
}
case detached: {
if (handle != null) {
handle.onDetached();
handle.detach();
}
break;
}
case event: {
if (handle != null) {
JSONObject plugin_data = null;
if (obj.has("plugindata"))
plugin_data = obj.getJSONObject("plugindata");
if (plugin_data != null) {
JSONObject data = null;
JSONObject jsep = null;
if (plugin_data.has("data"))
data = plugin_data.getJSONObject("data");
if (obj.has("jsep"))
jsep = obj.getJSONObject("jsep");
handle.onMessage(data, jsep);
}
}
}
}
} catch (JSONException ex) {
gatewayObserver.onCallbackError(ex.getMessage());
}
}
@Override
public void onOpen() {
createSession();
}
@Override
public void onClose() {
connected = false;
gatewayObserver.onCallbackError("Connection to janus server is closed");
}
@Override
public void onError(Exception ex) {
gatewayObserver.onCallbackError("Error connected to Janus gateway. Exception: " + ex.getMessage());
}
//endregion
//region SessionCreationCallbacks
@Override
public void onSessionCreationSuccess(JSONObject obj) {
try {
sessionId = new BigInteger(obj.getJSONObject("data").getString("id"));
keep_alive = new Thread(this, "KeepAlive");
keep_alive.start();
connected = true;
//TODO do we want to keep track of multiple sessions and servers?
gatewayObserver.onSuccess();
} catch (JSONException ex) {
gatewayObserver.onCallbackError(ex.getMessage());
}
}
//endregion
//region AttachPluginCallbacks
@Override
public void attachPluginSuccess(JSONObject obj, JanusSupportedPluginPackages plugin, IJanusPluginCallbacks pluginCallbacks) {
try {
BigInteger handle = new BigInteger(obj.getJSONObject("data").getString("id"));
JanusPluginHandle pluginHandle = new JanusPluginHandle(this, plugin, handle, pluginCallbacks);
synchronized (attachedPluginsLock) {
attachedPlugins.put(handle, pluginHandle);
}
pluginCallbacks.success(pluginHandle);
} catch (JSONException ex) {
//or do we want to use the pluginCallbacks.error(ex.getMessage());
gatewayObserver.onCallbackError(ex.getMessage());
}
}
//endregion
}