Support for DNS-SD cast discovery

This commit is contained in:
Adam Mills 2018-07-05 21:34:10 -04:00
parent 2368bed54b
commit 477f7b2a15
No known key found for this signature in database
GPG Key ID: 7733DCD6D0428689
3 changed files with 115 additions and 72 deletions

2
extern/GmsApi vendored

@ -1 +1 @@
Subproject commit 5af21c0ff2777e61caba5d111991cbb165167793 Subproject commit 540d2922599af4d9c3ef0174b45ebbc875aafd65

View File

@ -57,12 +57,12 @@ public class CastMediaRouteController extends MediaRouteProvider.RouteController
private String routeId; private String routeId;
private ChromeCast chromecast; private ChromeCast chromecast;
public CastMediaRouteController(CastMediaRouteProvider provider, String routeId, ChromeCast chromecast) { public CastMediaRouteController(CastMediaRouteProvider provider, String routeId, String address) {
super(); super();
this.provider = provider; this.provider = provider;
this.routeId = routeId; this.routeId = routeId;
this.chromecast = chromecast; this.chromecast = new ChromeCast(address);
} }
public boolean onControlRequest(Intent intent, MediaRouter.ControlRequestCallback callback) { public boolean onControlRequest(Intent intent, MediaRouter.ControlRequestCallback callback) {

View File

@ -19,6 +19,8 @@ package org.microg.gms.cast;
import android.content.Context; import android.content.Context;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Handler; import android.os.Handler;
@ -32,70 +34,27 @@ import android.util.Log;
import com.google.android.gms.common.images.WebImage; import com.google.android.gms.common.images.WebImage;
import com.google.android.gms.cast.CastDevice; import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.Thread; import java.lang.Thread;
import java.lang.Runnable; import java.lang.Runnable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.ChromeCasts;
import su.litvak.chromecast.api.v2.Status;
import su.litvak.chromecast.api.v2.ChromeCastsListener;
public class CastMediaRouteProvider extends MediaRouteProvider { public class CastMediaRouteProvider extends MediaRouteProvider {
private static final String TAG = CastMediaRouteProvider.class.getSimpleName(); private static final String TAG = CastMediaRouteProvider.class.getSimpleName();
private Map<String, ChromeCast> chromecasts = new HashMap<String, ChromeCast>(); private Map<String, MediaRouteDescriptor> routes = new HashMap<String, MediaRouteDescriptor>();
public CastMediaRouteProvider(Context context) { private NsdManager mNsdManager;
super(context); private NsdManager.DiscoveryListener mDiscoveryListener;
// TODO: Uncomment this to manually discover a chromecast on the local
// network. Discovery not yet implemented.
/*
InetAddress addr = null;
try {
addr = InetAddress.getByName("192.168.1.11");
} catch (UnknownHostException e) {
Log.d(TAG, "Chromecast status exception getting host: " + e.getMessage());
return;
}
onChromeCastDiscovered(addr);
*/
}
private void onChromeCastDiscovered(InetAddress address) {
ChromeCast chromecast = new ChromeCast(address.getHostAddress());
new Thread(new Runnable() {
public void run() {
Status status = null;
try {
status = chromecast.getStatus();
} catch (IOException e) {
Log.e(TAG, "Exception getting chromecast status: " + e.getMessage());
return;
}
Handler mainHandler = new Handler(CastMediaRouteProvider.this.getContext().getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
String routeId = address.getHostAddress();
CastMediaRouteProvider.this.chromecasts.put(routeId, chromecast);
publishRoutes();
}
});
}
}).start();
}
/** /**
* TODO: Mock control filters for chromecast; Will likely need to be * TODO: Mock control filters for chromecast; Will likely need to be
@ -105,38 +64,92 @@ public class CastMediaRouteProvider extends MediaRouteProvider {
static { static {
IntentFilter f2 = new IntentFilter(); IntentFilter f2 = new IntentFilter();
f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
f2.addCategory(CastMediaControlIntent.CATEGORY_CAST);
f2.addAction(MediaControlIntent.ACTION_PLAY); f2.addAction(MediaControlIntent.ACTION_PLAY);
CONTROL_FILTERS = new ArrayList<IntentFilter>(); CONTROL_FILTERS = new ArrayList<IntentFilter>();
CONTROL_FILTERS.add(f2); CONTROL_FILTERS.add(f2);
} }
public CastMediaRouteProvider(Context context) {
super(context);
mNsdManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE);
mDiscoveryListener = new NsdManager.DiscoveryListener() {
@Override @Override
public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { public void onDiscoveryStarted(String regType) {
Log.d(TAG, "unimplemented Method: onDiscoveryRequestChanged"); Log.d(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStarted");
} }
@Override @Override
public RouteController onCreateRouteController(String routeId) { public void onServiceFound(NsdServiceInfo service) {
ChromeCast chromecast = this.chromecasts.get(routeId); mNsdManager.resolveService(service, new NsdManager.ResolveListener() {
return new CastMediaRouteController(this, routeId, chromecast); @Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(TAG, "DiscoveryListener unimplemented Method: Resolve failed" + errorCode);
} }
private void publishRoutes() { @Override
MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder(); public void onServiceResolved(NsdServiceInfo serviceInfo) {
for(Map.Entry<String, ChromeCast> entry : this.chromecasts.entrySet()) { String name = serviceInfo.getServiceName();
String routeId = entry.getKey(); InetAddress host = serviceInfo.getHost();
ChromeCast chromecast = entry.getValue(); int port = serviceInfo.getPort();
try {
String id = new String(serviceInfo.getAttributes().get("id"), "UTF-8");
String deviceVersion = new String(serviceInfo.getAttributes().get("ve"), "UTF-8");
String friendlyName = new String(serviceInfo.getAttributes().get("fn"), "UTF-8");
String modelName = new String(serviceInfo.getAttributes().get("md"), "UTF-8");
String iconPath = new String(serviceInfo.getAttributes().get("ic"), "UTF-8");
int status = Integer.parseInt(new String(serviceInfo.getAttributes().get("st"), "UTF-8"));
onChromeCastDiscovered(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status);
} catch (UnsupportedEncodingException ex) {
Log.e(TAG, "Error getting cast details from DNS-SD response", ex);
return;
}
}
});
}
@Override
public void onServiceLost(NsdServiceInfo service) {
Log.d(TAG, "DiscoveryListener unimplemented Method: onServiceLost" + service);
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "DiscoveryListener unimplemented Method: onDiscoveryStopped " + serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "DiscoveryListener unimplemented Method: onStartDiscoveryFailed: Error code:" + errorCode);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "DiscoveryListener unimplemented Method: onStopDiscoveryFailed: Error code:" + errorCode);
}
};
}
private void onChromeCastDiscovered(
String id, String name, InetAddress host, int port, String
deviceVersion, String friendlyName, String modelName, String
iconPath, int status) {
if (!this.routes.containsKey(id)) {
// TODO: Capabilities
int capabilities = CastDevice.CAPABILITY_VIDEO_OUT | CastDevice.CAPABILITY_AUDIO_OUT;
Bundle extras = new Bundle(); Bundle extras = new Bundle();
CastDevice castDevice = new CastDevice( CastDevice castDevice = new CastDevice(id, name, host, port, deviceVersion, friendlyName, modelName, iconPath, status, capabilities);
routeId,
chromecast.getAddress()
);
castDevice.putInBundle(extras); castDevice.putInBundle(extras);
builder.addRoute(new MediaRouteDescriptor.Builder( this.routes.put(id, new MediaRouteDescriptor.Builder(
routeId, id,
routeId) friendlyName)
.setDescription("Chromecast") .setDescription(modelName)
.addControlFilters(CONTROL_FILTERS) .addControlFilters(CONTROL_FILTERS)
.setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV) .setDeviceType(MediaRouter.RouteInfo.DEVICE_TYPE_TV)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
@ -149,6 +162,36 @@ public class CastMediaRouteProvider extends MediaRouteProvider {
.build()); .build());
} }
Handler mainHandler = new Handler(this.getContext().getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
publishRoutes();
}
});
}
@Override
public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
if (request.isValid() && request.isActiveScan()) {
mNsdManager.discoverServices("_googlecast._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
} else {
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
}
@Override
public RouteController onCreateRouteController(String routeId) {
MediaRouteDescriptor descriptor = this.routes.get(routeId);
CastDevice castDevice = CastDevice.getFromBundle(descriptor.getExtras());
return new CastMediaRouteController(this, routeId, castDevice.getAddress());
}
private void publishRoutes() {
MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
for(MediaRouteDescriptor route : this.routes.values()) {
builder.addRoute(route);
}
this.setDescriptor(builder.build()); this.setDescriptor(builder.build());
} }
} }