package org.dyndns.jkiddo.raop.server; import java.io.IOException; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.inject.Inject; import javax.inject.Named; import org.dyndns.jkiddo.raop.ISpeakerListener; import org.dyndns.jkiddo.raop.server.airreceiver.RaopRtspPipelineFactory; import org.dyndns.jkiddo.service.dmap.MDNSResource; import org.dyndns.jkiddo.service.dmap.Util; import org.dyndns.jkiddo.zeroconf.IZeroconfManager; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.group.ChannelGroup; 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; import com.google.common.collect.Maps; public class RAOPResourceWrapper extends MDNSResource { public static final String RAOP_PORT_NAME = "RAOP_PORT_NAME"; private final ExecutorService executorService = Executors.newCachedThreadPool(); private final ChannelGroup allChannels = new DefaultChannelGroup(); private final ExecutionHandler channelExecutionHandler = new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(4, 0, 0)); private final byte[] hardwareAddressBytes = Util.getHardwareAddress(); private final String hardwareAddressString = Util.toHexString(Util.getHardwareAddress()); private final String name; private final IPlayingInformation playingInformation; private final ServerBootstrap airTunesRtspBootstrap; @Inject public RAOPResourceWrapper(final IZeroconfManager mDNS, @Named(RAOP_PORT_NAME) final Integer port, @Named(Util.APPLICATION_NAME) final String applicationName, final IPlayingInformation iPlayingInformation) throws IOException { super(mDNS, port); this.playingInformation = iPlayingInformation; this.name = applicationName; this.register(); final SimpleChannelUpstreamHandler channel = new SimpleChannelUpstreamHandler() { @Override public void channelOpen(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { allChannels.add(e.getChannel()); super.channelOpen(ctx, e); } }; airTunesRtspBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(executorService, executorService)); airTunesRtspBootstrap.setPipelineFactory(new RaopRtspPipelineFactory(hardwareAddressBytes, channelExecutionHandler, executorService, channel, playingInformation)); airTunesRtspBootstrap.setOption("reuseAddress", true); airTunesRtspBootstrap.setOption("child.tcpNoDelay", true); airTunesRtspBootstrap.setOption("child.keepAlive", true); allChannels.add(airTunesRtspBootstrap.bind(new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), this.port))); } // http://nto.github.io/AirPlay.html // http://cocoadev.com/wiki/AirTunesRendezvous @Override protected IZeroconfManager.ServiceInfo getServiceInfoToRegister() { final Map<String, String> map = Maps.newHashMap(); map.put("am", "Jolivia,1"); // map.put("am", "AirPort4,107"); map.put("ch", "2"); map.put("cn", "0,1"); map.put("da", "true"); map.put("ek", "1"); map.put("et", "0,1"); map.put("md", "0,1,2"); // map.put("fv", "76300.7"); map.put("pw", "false"); map.put("sm", "false"); // map.put("sf", "0x1"); map.put("sr", "44100"); map.put("ss", "16"); // map.put("sv", "false"); map.put("tp", "UDP"); // map.put("tp", "TCP,UDP"); map.put("txtvers", "1"); map.put("vn", "65537"); map.put("vs", "105.1"); // ... // map.put("txtvers", "1"); // map.put("tp", "UDP"); // map.put("ch", "2"); // map.put("ss", "16"); // map.put("sr", "44100"); // map.put("pw", "false"); // map.put("sm", "false"); // map.put("sv", "false"); // map.put("ek", "1"); // map.put("et", "0,1"); // map.put("cn", "0,1"); // map.put("vn", "3"); return new IZeroconfManager.ServiceInfo(ISpeakerListener.RAOP_TYPE, hardwareAddressString + "@" + name, this.port, map); } @Override public void deRegister() { super.deRegister(); airTunesRtspBootstrap.shutdown(); allChannels.close().awaitUninterruptibly(); executorService.shutdown(); channelExecutionHandler.releaseExternalResources(); } }