Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checking integration with iPhone #12

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
9 changes: 9 additions & 0 deletions .classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="org/phlo/AirReceiver/AirReceiver.java" kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" including="**/*.java" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

target/
23 changes: 23 additions & 0 deletions .project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>DroidAirPlay</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
6 changes: 6 additions & 0 deletions .settings/org.eclipse.core.resources.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Mon Jun 18 20:52:19 NZST 2012
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8
6 changes: 6 additions & 0 deletions .settings/org.eclipse.jdt.core.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Mon Jun 18 20:52:19 NZST 2012
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
5 changes: 5 additions & 0 deletions .settings/org.eclipse.m2e.core.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#Mon Jun 18 20:51:15 NZST 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
17 changes: 17 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
DroidAirPlay
===========
Rafael Almeida ([email protected])
July 2012

Overview
---------------
The ideia is to develop a AirPlay Receiver that runs on Android tablet and is part of a wider range of apps for in-car-systems.
The ideia is that the tablet is used as a car dashboard to control all car functions and the ability to stream audio, video e fotos
from iOS devices is achieved throught this project.

This project is based on the work done by Florian G. Pflug <fgp [at] phlo.org> project's AirReceiver.

AirReceiver is a desktop AirPlay receiver application, bellow is a full description:

NOTE: Work on DroidAirPlay has just worked (as of June 2012) so most of the code and project strucutre is the same as AirReceiver (since this is a fork from AirReceiver's repository).

AirReceiver
===========
Florian G. Pflug <fgp [at] phlo.org>
Expand Down
15 changes: 10 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.phlo</groupId>
<artifactId>AirReceiver</artifactId>
<version>1.2</version>
<groupId>nz.co.iswe</groupId>
<artifactId>DroidAirPlay</artifactId>
<version>0.1</version>
<packaging>jar</packaging>

<name>AirReceiver</name>
<url>https://github.com/fgp/AirReceiver</url>
<name> DroidAirPlay </name>
<url>https://github.com/pentateu/DroidAirPlay</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down Expand Up @@ -42,6 +42,11 @@
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>4.0.1.2</version>
</dependency>
</dependencies>

<build>
Expand Down
265 changes: 265 additions & 0 deletions src/main/java/nz/co/iswe/android/airplay/AirPlayServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
package nz.co.iswe.android.airplay;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;

import nz.co.iswe.android.airplay.network.NetworkUtils;
import nz.co.iswe.android.airplay.network.raop.RaopRtspPipelineFactory;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;

/**
* Android AirPlay Server Implementation
*
* @author Rafael Almeida
*
*/
public class AirPlayServer implements Runnable {

private static final Logger LOG = Logger.getLogger(AirPlayServer.class.getName());

/**
* The AirTunes/RAOP service type
*/
static final String AIR_TUNES_SERVICE_TYPE = "_raop._tcp.local.";

/**
* The AirTunes/RAOP M-DNS service properties (TXT record)
*/
static final Map<String, String> AIRTUNES_SERVICE_PROPERTIES = map(
"txtvers", "1",
"tp", "UDP",
"ch", "2",
"ss", "16",
"sr", "44100",
"pw", "false",
"sm", "false",
"sv", "false",
"ek", "1",
"et", "0,1",
"cn", "0,1",
"vn", "3"
);

private static AirPlayServer instance = null;
public static AirPlayServer getIstance(){
if(instance == null){
instance = new AirPlayServer();
}
return instance;
}

/**
* Global executor service. Used e.g. to initialize the various netty channel factories
*/
protected ExecutorService executorService;

/**
* Channel execution handler. Spreads channel message handling over multiple threads
*/
protected ExecutionHandler channelExecutionHandler;

/**
* All open RTSP channels. Used to close all open challens during shutdown.
*/
protected ChannelGroup channelGroup;

/**
* JmDNS instances (one per IP address). Used to unregister the mDNS services
* during shutdown.
*/
protected List<JmDNS> jmDNSInstances;

/**
* The AirTunes/RAOP RTSP port
*/
private int rtspPort = 5000; //default value

private AirPlayServer(){
//create executor service
executorService = Executors.newCachedThreadPool();

//create channel execution handler
channelExecutionHandler = new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(4, 0, 0));

//channel group
channelGroup = new DefaultChannelGroup();

//list of mDNS services
jmDNSInstances = new java.util.LinkedList<JmDNS>();
}

public int getRtspPort() {
return rtspPort;
}

public void setRtspPort(int rtspPort) {
this.rtspPort = rtspPort;
}

public void run() {

startService();
}

private void startService() {
/* Make sure AirPlay Server shuts down gracefully */
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
onShutdown();
}
}));

LOG.info("VM Shutdown Hook added sucessfully!");

/* Create AirTunes RTSP server */
final ServerBootstrap airTunesRtspBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(executorService, executorService));
airTunesRtspBootstrap.setPipelineFactory(new RaopRtspPipelineFactory());
airTunesRtspBootstrap.setOption("reuseAddress", true);
airTunesRtspBootstrap.setOption("child.tcpNoDelay", true);
airTunesRtspBootstrap.setOption("child.keepAlive", true);

try {
channelGroup.add(airTunesRtspBootstrap.bind(new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), getRtspPort())));
}
catch (UnknownHostException e) {
LOG.log(Level.SEVERE, "Failed to bind RTSP Bootstrap on port: " + getRtspPort(), e);
}

LOG.info("Launched RTSP service on port " + getRtspPort());

//get Network details
NetworkUtils networkUtils = NetworkUtils.getInstance();

String hostName = networkUtils.getHostUtils();
String hardwareAddressString = networkUtils.getHardwareAddressString();

try {
/* Create mDNS responders. */
synchronized(jmDNSInstances) {
for(final NetworkInterface iface: Collections.list(NetworkInterface.getNetworkInterfaces())) {
if ( iface.isLoopback() ){
continue;
}
if ( iface.isPointToPoint()){
continue;
}
if ( ! iface.isUp()){
continue;
}

for(final InetAddress addr: Collections.list(iface.getInetAddresses())) {
if ( ! (addr instanceof Inet4Address) && ! (addr instanceof Inet6Address) ){
continue;
}

try {
/* Create mDNS responder for address */
final JmDNS jmDNS = JmDNS.create(addr, hostName + "-jmdns");
jmDNSInstances.add(jmDNS);

/* Publish RAOP service */
final ServiceInfo airTunesServiceInfo = ServiceInfo.create(
AIR_TUNES_SERVICE_TYPE,
hardwareAddressString + "@" + hostName + " (" + iface.getName() + ")",
getRtspPort(),
0 /* weight */, 0 /* priority */,
AIRTUNES_SERVICE_PROPERTIES
);
jmDNS.registerService(airTunesServiceInfo);
LOG.info("Registered AirTunes service '" + airTunesServiceInfo.getName() + "' on " + addr);
}
catch (final Throwable e) {
LOG.log(Level.SEVERE, "Failed to publish service on " + addr, e);
}
}
}
}

}
catch (SocketException e) {
LOG.log(Level.SEVERE, "Failed register mDNS services", e);
}
}

//When the app is shutdown
protected void onShutdown() {
/* Close channels */
final ChannelGroupFuture allChannelsClosed = channelGroup.close();

/* Stop all mDNS responders */
synchronized(jmDNSInstances) {
for(final JmDNS jmDNS: jmDNSInstances) {
try {
jmDNS.unregisterAllServices();
LOG.info("Unregistered all services on " + jmDNS.getInterface());
}
catch (final IOException e) {
LOG.log(Level.WARNING, "Failed to unregister some services", e);

}
}
}

/* Wait for all channels to finish closing */
allChannelsClosed.awaitUninterruptibly();

/* Stop the ExecutorService */
executorService.shutdown();

/* Release the OrderedMemoryAwareThreadPoolExecutor */
channelExecutionHandler.releaseExternalResources();

}

/**
* Map factory. Creates a Map from a list of keys and values
*
* @param keys_values key1, value1, key2, value2, ...
* @return a map mapping key1 to value1, key2 to value2, ...
*/
private static Map<String, String> map(final String... keys_values) {
assert keys_values.length % 2 == 0;
final Map<String, String> map = new java.util.HashMap<String, String>(keys_values.length / 2);
for(int i=0; i < keys_values.length; i+=2)
map.put(keys_values[i], keys_values[i+1]);
return Collections.unmodifiableMap(map);
}

public ChannelHandler getChannelExecutionHandler() {
return channelExecutionHandler;
}

public ChannelGroup getChannelGroup() {
return channelGroup;
}

public ExecutorService getExecutorService() {
return executorService;
}

}
Loading