I need to start 2 services simultaneously (i.e airplay and airtunes) using jmdns. I'm able to start one service using jmdns but when I register 2 services simultaneously it gives me exception while sending data from IOS.
Here is my code:
void startDNSService() {
final JmDNS jmDNS = JmDNS.create(address); /* Create mDNS responder for address */
s_jmDNSInstances.add(jmDNS);
jmDNS.registerServiceType(AIR_TUNES_SERVICE_TYPE); /* Initialization service name */
String name = Utils.servName.length() > 0 ? Utils.servName : Utils.HardwareAddressString + "@X" + android.os.Build.MODEL;
Utils.servName = name;
/* Publish RAOP service */
final ServiceInfo airPlayServiceInfo = ServiceInfo.create(
AirPlayConstant.type,name, /* Service Name */
AirPlayConstant.port, 0 /* weight */, 0 /* priority */,
AirPlayConstant.getTxtHead());
jmDNS.registerService(airPlayServiceInfo);
final JmDNS jmDNS = JmDNS.create(InetAddress.getByName(ip),"audio");//AirTunes Service Strart code:
jmDNSInstances.add(jmDNS);// Create mDNS responder for address
jmDNS.registerServiceType(AirPlayConstant.type);
// Publish RAOP service
final ServiceInfo airTunesServiceInfo = ServiceInfo.create(AIR_TUNES_SERVICE_TYPE, "Airplay", getRtspPort(), 0 , 0 ,
AIRTUNES_SERVICE_PROPERTIES);
jmDNS.registerService(airTunesServiceInfo);
}
I've got ChannelCloseException
while sharing audio from iTunes through AirTunes service. Video works well.
When we are running ONLY Airtunes service on Android and share audio from iOS, on Android we recieve something like:
DefaultHttpRequest(chunked: false)
OPTIONS * RTSP/1.0
CSeq: 0
X-Apple-Device-ID: 0xf4f95172ac4a
Apple-Challenge: Vil4gZ2Sihm72isehrFb2g==
DACP-ID: 9A4EC4744A1C604D
Active-Remote: 1509410937
User-Agent: AirPlay/200.54
and your Android needs to send following response to iOS device:
RTSP/1.0 200 OK
Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER
CSeq: 0
Audio-Jack-Status: connected; type=analog
Apple-Response: CXMubVPs0Dy3IsHCNrOLTJbaVLhhL8eXX8+ctx2fJ62BeKm90HCYn1H0iYeBW52SKDtC97LI2JOK5U8kArfcDJX58F4+9iEAoFNrGBEcveN0PVCrZTmyUX1m5ldNnJYjAArdr8QJb9TrkMHpItP4lbv13DgWnv9qYRsAXA5okLpnbiCmeTiA3or3abK0OiVShjFlhcYetb8VfolCs6+907pRKQsJV4FS4XXY2c/wwlGQAh1ifru/QALxFKye7akiaegVnMH/+k68drFWNlUVPBJua20yc/LAT9Hbhz4yEBvehgh/CoBRYaOHu3VxwIjIlKRkqwobONk7hA687ASp7A==
BUT When you start Airplay and Airtunes services on a device (your android server) and when you try to share audio from iOS device, iOS device sends POST/fp-setup request to your Android requesting a fair-play setup of rtsp stream as follows:
POST /fp-setup RTSP/1.0
X-Apple-ET: 32
CSeq: 0
X-Apple-Device-ID: 0xf4f95172ac4a
DACP-ID: 9A4EC4744A1C604D
Active-Remote: 1853152273
Content-Type: application/octet-stream
Content-Length: 16
User-Agent: AirPlay/200.54
contents:
[F, P, L, Y, , , , ,,,,,,,,,,,,,
you need to send its response back to iOS device. This is a kind of challenge.
In your RaopAudioHandler.java send response to POST /fp-setup RTSP/1.0
as below:
@Override
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent evt) throws Exception {
final HttpRequest req = (HttpRequest) evt.getMessage();
final HttpMethod method = req.getMethod();
LogManager.w("messageReceived : HttpMethod: " + method);
if (method.toString().equals("POST") && req.getUri().equals("/fp-setup")) {
// 2 1 1 -> 4 : 02 00 02 bb
byte fply_1[] = {
0x46, 0x50, 0x4c, 0x59, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x02, (byte) 0xbb
};
// 2 1 2 -> 130 : 02 02 xxx
byte fply_2[] = {
0x46, 0x50, 0x4c, 0x59, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, (byte) 0x82, 0x02, 0x02, 0x2f, 0x7b, 0x69, (byte) 0xe6, (byte) 0xb2, 0x7e, (byte) 0xbb, (byte) 0xf0, 0x68, 0x5f, (byte) 0x98, 0x54, 0x7f, 0x37, (byte) 0xce, (byte) 0xcf, (byte) 0x87, 0x06, (byte) 0x99, 0x6e, 0x7e, 0x6b, 0x0f, (byte) 0xb2, (byte) 0xfa, 0x71, 0x20, 0x53, (byte) 0xe3, (byte) 0x94, (byte) 0x83, (byte) 0xda, 0x22, (byte) 0xc7, (byte) 0x83, (byte) 0xa0, 0x72, 0x40, 0x4d, (byte) 0xdd, 0x41, (byte) 0xaa, 0x3d, 0x4c, 0x6e, 0x30, 0x22, 0x55, (byte) 0xaa, (byte) 0xa2, (byte) 0xda, 0x1e, (byte) 0xb4, 0x77, (byte) 0x83, (byte) 0x8c, 0x79, (byte) 0xd5, 0x65, 0x17, (byte) 0xc3, (byte) 0xfa, 0x01, 0x54, 0x33, (byte) 0x9e, (byte) 0xe3, (byte) 0x82, (byte) 0x9f, 0x30, (byte) 0xf0, (byte) 0xa4, (byte) 0x8f, 0x76, (byte) 0xdf, 0x77, 0x11, 0x7e, 0x56, (byte)(byte) 0x9e, (byte)(byte) 0xf3, (byte) 0x95, (byte) 0xe8, (byte) 0xe2, (byte) 0x13, (byte) 0xb3, (byte) 0x1e, (byte) 0xb6, (byte) 0x70, (byte) 0xec, (byte) 0x5a, (byte) 0x8a, (byte) 0xf2, (byte) 0x6a, (byte) 0xfc, (byte) 0xbc, (byte) 0x89, (byte) 0x31, (byte) 0xe6, (byte) 0x7e, (byte) 0xe8, (byte) 0xb9, (byte) 0xc5, (byte) 0xf2, (byte) 0xc7, (byte) 0x1d, (byte) 0x78, (byte) 0xf3, (byte) 0xef, (byte) 0x8d, 0x61, (byte) 0xf7, (byte) 0x3b, (byte) 0xcc, (byte) 0x17, (byte) 0xc3, (byte) 0x40, (byte) 0x23, (byte) 0x52, (byte) 0x4a, (byte) 0x8b, (byte) 0x9c, (byte) 0xb1, (byte) 0x75, (byte) 0x05, (byte) 0x66, (byte) 0xe6, (byte) 0xb3
};
// 2 1 3 -> 152
// 4 : 02 8f 1a 9c
// 128 : xxx
// 20 : 5b ed 04 ed c3 cd 5f e6 a8 28 90 3b 42 58 15 cb 74 7d ee 85
byte fply_3[] = {
0x46, (byte) 0x50, (byte) 0x4c, (byte) 0x59, (byte) 0x02, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x98, (byte) 0x02, (byte) 0x8f, 0x1a, (byte) 0x9c, (byte) 0x6e, (byte) 0x73, (byte) 0xd2, (byte) 0xfa, (byte) 0x62, (byte) 0xb2, (byte) 0xb2, (byte) 0x07, (byte) 0x6f, (byte) 0x52, (byte) 0x5f, (byte) 0xe5, (byte) 0x72, (byte) 0xa5, (byte) 0xac, (byte) 0x4d, (byte) 0x19, (byte) 0xb4, (byte) 0x7c, (byte) 0xd8, (byte) 0x07, (byte) 0x1e, (byte) 0xdb, (byte) 0xbc, (byte) 0x98, (byte) 0xae, (byte) 0x7e, (byte) 0x4b, (byte) 0xb4, (byte) 0xb7, 0x2a, (byte) 0x7b, (byte) 0x5e, (byte) 0x2b, (byte) 0x8a, (byte) 0xde, (byte) 0x94, (byte) 0x4b, (byte) 0x1d, (byte) 0x59, (byte) 0xdf, (byte) 0x46, (byte) 0x45, (byte) 0xa3, (byte) 0xeb, (byte) 0xe2, 0x6d, (byte) 0xa2, (byte) 0x83, (byte) 0xf5, (byte) 0x06, (byte) 0x53, (byte) 0x8f, (byte) 0x76, (byte) 0xe7, (byte) 0xd3, (byte) 0x68, (byte) 0x3c, (byte) 0xeb, (byte) 0x1f, (byte) 0x80, (byte) 0x0e, 0x68, (byte) 0x9e, (byte) 0x27, (byte) 0xfc, (byte) 0x47, (byte) 0xbe, (byte) 0x3d, (byte) 0x8f, (byte) 0x73, (byte) 0xaf, (byte) 0xa1, (byte) 0x64, (byte) 0x39, (byte) 0xf7, (byte) 0xa8, (byte) 0xf7, (byte) 0xc2, (byte) 0xc8, (byte) 0xb0, (byte) 0x20, (byte) 0x0c, (byte) 0x85, (byte) 0xd6, (byte) 0xae, (byte) 0xb7, (byte) 0xb2, (byte) 0xd4, (byte) 0x25, (byte) 0x96, (byte) 0x77, (byte) 0x91, (byte) 0xf8, (byte) 0x83, (byte) 0x68, (byte) 0x10, (byte) 0xa1, (byte) 0xa9, (byte) 0x15, (byte) 0x4a, (byte) 0xa3, (byte) 0x37, (byte) 0x8c, (byte) 0xb7, (byte) 0xb9, (byte) 0x89, (byte) 0xbf, (byte) 0x86, (byte) 0x6e, (byte) 0xfb, (byte) 0x95, (byte) 0x41, (byte) 0xff, (byte) 0x03, (byte) 0x57, (byte) 0x61, (byte) 0x05, (byte) 0x00, (byte) 0x73, (byte) 0xcc, (byte) 0x06, (byte) 0x7e, (byte) 0x4f, (byte) 0xc7, (byte) 0x96, (byte) 0xae, (byte) 0xba, (byte) 0x5b, (byte) 0xed, (byte) 0x04, (byte) 0xed, (byte) 0xc3, (byte) 0xcd, (byte) 0x5f, (byte) 0xe6, (byte) 0xa8, (byte) 0x28, (byte) 0x90, (byte) 0x3b, (byte) 0x42, (byte) 0x58, 0x15, (byte) 0xcb, (byte) 0x74, (byte) 0x7d, (byte) 0xee, (byte) 0x85
};
// 2 1 4 -> 20 : 5b ed 04 ed c3 cd 5f e6 a8 28 90 3b 42 58 15 cb 74 7d ee 85
byte fply_4[] = {
0x46, (byte) 0x50, (byte) 0x4c, (byte) 0x59, (byte) 0x02, (byte) 0x01, (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x14, (byte) 0x5b, (byte) 0xed, (byte) 0x04, (byte) 0xed, (byte) 0xc3, (byte) 0xcd, (byte) 0x5f, (byte) 0xe6, (byte) 0xa8, (byte) 0x28, (byte) 0x90, (byte) 0x3b, (byte) 0x42, (byte) 0x58, (byte) 0x15, (byte) 0xcb, (byte) 0x74, 0x7d, (byte) 0xee, (byte) 0x85
};
ChannelBuffer content = req.getContent();
int length = content.capacity();
byte fply_header[] = new byte[12];
content.getBytes(0, fply_header);
length = fply_header.length;
int rlocation = fply_header.length;
int rlength = content.capacity() - fply_header.length;
ChannelBuffer payload = content.copy(rlocation, rlength);
if (fply_header[6] == 1) {
int rloc = 12 + 2;
int rl = 1;
content.getBytes(13, fply_2, rloc, rl);
replyOK(ctx, fply_2, req);
} else if (fply_header[6] == 3) {
int rloc = payload.capacity() - 20;
int rl = 20;
byte data[] = new byte[12];
System.arraycopy(fply_4, 0, data, 0, data.length);
ChannelBuffer sd = payload.copy(rloc, rl);
byte[] subData = new byte[sd.capacity()];
sd.getBytes(0, subData);
byte[] rep = new byte[data.length + subData.length];
System.arraycopy(data, 0, rep, 0, data.length);
System.arraycopy(subData, 0, rep, data.length, subData.length);
String s = new String(rep);
Log.i("response", s);
replyOK(ctx, rep, req);
}
return;
} else if (method.toString().equals("POST") && req.getUri().equals("/auth-setup")) {
ChannelBuffer content = req.getContent();
byte[] rep = new byte[content.capacity()];
content.getBytes(0, rep);
replyOK(ctx, rep, req);
return;
} else if (RaopRtspMethods.ANNOUNCE.equals(method)) {
announceReceived(ctx, req);
return;
}
else if (RaopRtspMethods.SETUP.equals(method)) {...............
}
}
And:
public synchronized void replyOK(final ChannelHandlerContext ctx, byte[] data, final HttpRequest req) throws Exception {
final HttpResponse response = new DefaultHttpResponse(RtspVersions.RTSP_1_0, RtspResponseStatuses.OK);
if (data.length > 0) {
response.addHeader("Content-Type", "application/octet-stream");
response.addHeader("Content-Length", Integer.toString(data.length));
}
response.addHeader("Server", "AirTunes/104.29");
response.addHeader("X-Apple-ET", req.getHeader("X-Apple-ET"));
response.addHeader("CSeq", req.getHeader("CSeq"));
response.setContent(ChannelBuffers.wrappedBuffer(data));
ctx.getChannel().write(response);
}
Let me know if this solves the issue for you.