Skip to content

Commit

Permalink
WIP: Switch from jmdns (buggy) to NsdManager
Browse files Browse the repository at this point in the history
Unfortunately, this limits us to Android 4.1 (API level 16) and up, but this covers over 71% of Android devices currently in use.
  • Loading branch information
Jeremy White committed Jun 12, 2014
1 parent c998b54 commit 9fbc29f
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 122 deletions.
4 changes: 2 additions & 2 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="10" />
android:minSdkVersion="16"
android:targetSdkVersion="16" />

<application />
</manifest>
Binary file removed libs/jmdns-3.4.1.jar
Binary file not shown.
265 changes: 146 additions & 119 deletions src/com/connectsdk/discovery/provider/ZeroconfDiscoveryProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,163 +20,77 @@

package com.connectsdk.discovery.provider;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdManager.DiscoveryListener;
import android.net.nsd.NsdManager.ResolveListener;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;

import com.connectsdk.core.Util;
import com.connectsdk.discovery.DiscoveryProvider;
import com.connectsdk.discovery.DiscoveryProviderListener;
import com.connectsdk.service.AirPlayService;
import com.connectsdk.service.command.ServiceCommandError;
import com.connectsdk.service.config.ServiceDescription;

public class ZeroconfDiscoveryProvider implements DiscoveryProvider {
JmDNS jmdns;
public static String TAG = "Connect SDK";

private final static int RESCAN_INTERVAL = 10000;
private Timer dataTimer;

private NsdManager mNsdManager;

List<JSONObject> serviceFilters;

ServiceListener jmdnsListener = new ServiceListener() {

@Override
public void serviceResolved(ServiceEvent ev) {
@SuppressWarnings("deprecation")
String ipAddress = ev.getInfo().getHostAddress();
if (!Util.isIPv4Address(ipAddress)) {
// Currently, we only support ipv4 for airplay service
return;
}

String uuid = ipAddress;
String friendlyName = ev.getInfo().getName();
int port = ev.getInfo().getPort();

ServiceDescription oldService = services.get(uuid);

ServiceDescription newService;
if ( oldService == null ) {
newService = new ServiceDescription(ev.getInfo().getType(), uuid, ipAddress);
}
else {
newService = oldService;
newService.setIpAddress(ipAddress);
}

newService.setServiceID(AirPlayService.ID);
newService.setFriendlyName(friendlyName);
newService.setPort(port);

services.put(uuid, newService);

for ( DiscoveryProviderListener listener: serviceListeners) {
listener.onServiceAdded(ZeroconfDiscoveryProvider.this, newService);
}
}

@Override
public void serviceRemoved(ServiceEvent ev) {
@SuppressWarnings("deprecation")
String uuid = ev.getInfo().getHostAddress();
ServiceDescription service = services.get(uuid);

if ( service != null ) {
services.remove(uuid);

for ( DiscoveryProviderListener listener: serviceListeners) {
listener.onServiceRemoved(ZeroconfDiscoveryProvider.this, service);
}
}
}

@Override
public void serviceAdded(ServiceEvent event) {
// Required to force serviceResolved to be called again
// (after the first search)
jmdns.requestServiceInfo(event.getType(), event.getName(), 1);
}
};
private DiscoveryListener mDnsListener;
private ResolveListener mResolveFoundListener;
private ResolveListener mResolveLostListener;

private ConcurrentHashMap<String, ServiceDescription> services;
private CopyOnWriteArrayList<DiscoveryProviderListener> serviceListeners;

public ZeroconfDiscoveryProvider(Context context) {
initJmDNS();

services = new ConcurrentHashMap<String, ServiceDescription>(8, 0.75f, 2);
serviceListeners = new CopyOnWriteArrayList<DiscoveryProviderListener>();
serviceFilters = new ArrayList<JSONObject>();
}

private void initJmDNS() {
Util.runInBackground(new Runnable() {

@Override
public void run() {
try {
jmdns = JmDNS.create();
} catch (IOException e) {
e.printStackTrace();
}
}
});

mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
initListener();
}

@Override
public void start() {
dataTimer = new Timer();
MDNSSearchTask sendSearch = new MDNSSearchTask();
dataTimer.schedule(sendSearch, 100, RESCAN_INTERVAL);
}

private class MDNSSearchTask extends TimerTask {

@Override
public void run() {
if (jmdns != null) {
for (JSONObject searchTarget : serviceFilters) {
try {
String filter = searchTarget.getString("filter");
jmdns.addServiceListener(filter, jmdnsListener);
} catch (JSONException e) {
e.printStackTrace();
}
};
Iterator<JSONObject> iterator = serviceFilters.iterator();

while (iterator.hasNext()) {
JSONObject filter = iterator.next();
String filterName = null;

try {
filterName = filter.getString("filter");
} catch (JSONException ex) {
// do nothing
}

if (filterName == null)
continue;

mNsdManager.discoverServices(filterName, NsdManager.PROTOCOL_DNS_SD, mDnsListener);
}
}

@Override
public void stop() {
if (dataTimer != null) {
dataTimer.cancel();
}

if (jmdns != null) {
for (JSONObject searchTarget : serviceFilters) {
try {
String filter = searchTarget.getString("filter");
jmdns.removeServiceListener(filter, jmdnsListener);
} catch (JSONException e) {
e.printStackTrace();
}
};
}
mNsdManager.stopServiceDiscovery(mDnsListener);
}

@Override
Expand Down Expand Up @@ -235,4 +149,117 @@ public void removeDeviceFilter(JSONObject parameters) {
public boolean isEmpty() {
return serviceFilters.size() == 0;
}

private void initListener() {
mResolveFoundListener = new ResolveListener() {

@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
if (!(serviceInfo.getHost() instanceof Inet4Address)) {
// Currently, we only support ipv4 for airplay service
return;
}

String ipAddress = serviceInfo.getHost().toString();

if (ipAddress.charAt(0) == '/')
ipAddress = ipAddress.substring(1);

String uuid = ipAddress;
String friendlyName = serviceInfo.getServiceName();

try {
byte[] utf8 = friendlyName.getBytes("UTF-8");
friendlyName = new String(utf8, "UTF-8");
} catch (UnsupportedEncodingException e) { }

int port = serviceInfo.getPort();

Log.d(TAG, "Zeroconf found device " + friendlyName + " with service type " + serviceInfo.getServiceType());

ServiceDescription oldService = services.get(uuid);

ServiceDescription newService;
if ( oldService == null ) {
newService = new ServiceDescription(serviceInfo.getServiceType(), uuid, ipAddress);
}
else {
newService = oldService;
newService.setIpAddress(ipAddress);
}

newService.setServiceID(AirPlayService.ID);
newService.setFriendlyName(friendlyName);
newService.setPort(port);

services.put(uuid, newService);

for ( DiscoveryProviderListener listener: serviceListeners) {
listener.onServiceAdded(ZeroconfDiscoveryProvider.this, newService);
}
}

@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { }
};

mResolveLostListener = new ResolveListener() {

@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
String ipAddress = serviceInfo.getHost().toString();

if (ipAddress.charAt(0) == '/')
ipAddress = ipAddress.substring(1);

ServiceDescription service = services.get(ipAddress);

if ( service != null ) {
services.remove(ipAddress);

Log.d(TAG, "Zeroconf lost device " + serviceInfo.getServiceName() + " with service type " + serviceInfo.getServiceType());

for ( DiscoveryProviderListener listener: serviceListeners) {
listener.onServiceRemoved(ZeroconfDiscoveryProvider.this, service);
}
}
}

@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { }
};

mDnsListener = new DiscoveryListener() {

@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
for ( DiscoveryProviderListener listener: serviceListeners) {
listener.onServiceDiscoveryFailed(ZeroconfDiscoveryProvider.this, new ServiceCommandError(errorCode, "Discovery failed", serviceType));
}
}

@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
for ( DiscoveryProviderListener listener: serviceListeners) {
listener.onServiceDiscoveryFailed(ZeroconfDiscoveryProvider.this, new ServiceCommandError(errorCode, "Discovery failed", serviceType));
}
}

@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
mNsdManager.resolveService(serviceInfo, mResolveFoundListener);
}

@Override
public void onServiceLost(NsdServiceInfo serviceInfo) {
mNsdManager.resolveService(serviceInfo, mResolveLostListener);
}

@Override
public void onDiscoveryStopped(String serviceType) { }

@Override
public void onDiscoveryStarted(String serviceType) { }
};
}
}
2 changes: 1 addition & 1 deletion src/com/connectsdk/service/AirPlayService.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static JSONObject discoveryParameters() {

try {
params.put("serviceId", ID);
params.put("filter", "_airplay._tcp.local.");
params.put("filter", "_airplay._tcp.");
} catch (JSONException e) {
e.printStackTrace();
}
Expand Down

2 comments on commit 9fbc29f

@khk624
Copy link
Contributor

@khk624 khk624 commented on 9fbc29f Jun 13, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've switched from nsdManager to jmdns, because nsdManager was buggy. What was the resaon you switched it back?

One example I remember was, when I used nsdManager, it receives two different response from one apple device, and they have different friendly name.

@iheart2code
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jmdns is pretty buggy as well.

Please sign in to comment.