Package org.phlo.AirReceiver

Source Code of org.phlo.AirReceiver.AirReceiver

/*
* This file is part of AirReceiver.
*
* AirReceiver is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.

* AirReceiver is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with AirReceiver.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.phlo.AirReceiver;

import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.*;
import java.util.logging.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import javax.jmdns.*;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.group.*;
import org.jboss.netty.channel.socket.nio.*;
import org.jboss.netty.handler.execution.*;

public class AirReceiver {
  /* Load java.util.logging configuration */
  static {
    final InputStream loggingPropertiesStream =
      AirReceiver.class.getClassLoader().getResourceAsStream("logging.properties");
      try {
      LogManager.getLogManager().readConfiguration(loggingPropertiesStream);
    }
      catch (final IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
  }
 
  private static final Logger s_logger = Logger.getLogger(AirReceiver.class.getName());

  public static final String Version = getVersion();
 
  /**
   * The hardware (MAC) address of the emulated Airport Express
   */
  public static final byte[] HardwareAddressBytes = getHardwareAddress();

  /**
   * The hardware (MAC) address as a hexadecimal string
   */
  public static final String HardwareAddressString = toHexString(HardwareAddressBytes);

  /**
   * The hostname of the emulated Airport Express
   */
  public static final String HostName = getHostName();

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

  /**
   * The AirTunes/RAOP RTSP port
   */
  public static final short AirtunesServiceRTSPPort = 5000;

  /**
   * The AirTunes/RAOP M-DNS service properties (TXT record)
   */
  public static final Map<String, String> AirtunesServiceProperties = 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"
  );

  /**
   * Global executor service. Used e.g. to initialize the various netty channel factories
   */
  public static final ExecutorService ExecutorService = Executors.newCachedThreadPool();

  /**
   * Channel execution handler. Spreads channel message handling over multiple threads
   */
  public static final ExecutionHandler ChannelExecutionHandler = new ExecutionHandler(
    new OrderedMemoryAwareThreadPoolExecutor(4, 0, 0)
  );

  /**
   * Message dispayed in the "About" dialog
   */
  private static final String AboutMessage =
    "   * AirReceiver " + Version + " *\n" +
    "\n" +
    "Copyright (c) 2011 Florian G. Pflug\n" +
    "\n" +
    "AirReceiver is free software: you can redistribute it and/or modify\n" +
    "it under the terms of the GNU General Public License as published by\n" +
    "the Free Software Foundation, either version 3 of the License, or\n" +
    "(at your option) any later version.\n" +
    "\n" +
    "AirReceiver is distributed in the hope that it will be useful,\n" +
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +
    "GNU General Public License for more details.\n" +
    "\n" +
    "You should have received a copy of the GNU General Public License\n" +
    "along with AirReceiver.  If not, see <http://www.gnu.org/licenses/>." +
    "\n\n" +
    "   * Java ALAC Decoder *\n" +
    "\n" +
    "Copyright (c) 2011 Peter McQuillan";

  /**
   * JmDNS instances (one per IP address). Used to unregister the mDNS services
   * during shutdown.
   */
  private static final List<JmDNS> s_jmDNSInstances = new java.util.LinkedList<JmDNS>();

  /**
   * All open RTSP channels. Used to close all open challens during shutdown.
   */
  private static ChannelGroup s_allChannels = new DefaultChannelGroup();

  /**
   * Channel handle that registeres the channel to be closed on shutdown
   */
  public static final ChannelHandler CloseChannelOnShutdownHandler = new SimpleChannelUpstreamHandler() {
    @Override
    public void channelOpen(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
      s_allChannels.add(e.getChannel());
      super.channelOpen(ctx, e);
    }
  };
   
  /**
   * 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);
  }

  /**
   * Decides whether or nor a given MAC address is the address of some
   * virtual interface, like e.g. VMware's host-only interface (server-side).
   *
   * @param addr a MAC address
   * @return true if the MAC address is unsuitable as the device's hardware address
   */
  public static boolean isBlockedHardwareAddress(final byte[] addr) {
    if ((addr[0] & 0x02) != 0)
      /* Locally administered */
      return true;
    else if ((addr[0] == 0x00) && (addr[1] == 0x50) && (addr[2] == 0x56))
      /* VMware */
      return true;
    else if ((addr[0] == 0x00) && (addr[1] == 0x1C) && (addr[2] == 0x42))
      /* Parallels */
      return true;
    else if ((addr[0] == 0x00) && (addr[1] == 0x25) && (addr[2] == (byte)0xAE))
      /* Microsoft */
      return true;
    else
      return false;
  }
 
  /**
   * Reads the version from the version.properties file
   * @return the version
   */
  private static String getVersion() {
    Properties versionProperties = new Properties();
    final InputStream versionPropertiesStream =
      AirReceiver.class.getClassLoader().getResourceAsStream("version.properties");
      try {
        versionProperties.load(versionPropertiesStream);
    }
      catch (final IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
      return versionProperties.getProperty("org.phlo.AirReceiver.version");
  }

  /**
   * Returns a suitable hardware address.
   *
   * @return a MAC address
   */
  private static byte[] getHardwareAddress() {
    try {
      /* Search network interfaces for an interface with a valid, non-blocked hardware address */
        for(final NetworkInterface iface: Collections.list(NetworkInterface.getNetworkInterfaces())) {
          if (iface.isLoopback())
            continue;
          if (iface.isPointToPoint())
            continue;

          try {
            final byte[] ifaceMacAddress = iface.getHardwareAddress();
            if ((ifaceMacAddress != null) && (ifaceMacAddress.length == 6) && !isBlockedHardwareAddress(ifaceMacAddress)) {
              s_logger.info("Hardware address is " + toHexString(ifaceMacAddress) + " (" + iface.getDisplayName() + ")");
                return Arrays.copyOfRange(ifaceMacAddress, 0, 6);
            }
          }
          catch (final Throwable e) {
            /* Ignore */
          }
        }
    }
    catch (final Throwable e) {
      /* Ignore */
    }

    /* Fallback to the IP address padded to 6 bytes */
    try {
      final byte[] hostAddress = Arrays.copyOfRange(InetAddress.getLocalHost().getAddress(), 0, 6);
      s_logger.info("Hardware address is " + toHexString(hostAddress) + " (IP address)");
      return hostAddress;
    }
    catch (final Throwable e) {
      /* Ignore */
    }

    /* Fallback to a constant */
    s_logger.info("Hardware address is 00DEADBEEF00 (last resort)");
    return new byte[] {(byte)0x00, (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF, (byte)0x00};
  }

  /**
   * Returns the machine's host name
   *
   * @return the host name
   */
  private static String getHostName() {
    try {
      return InetAddress.getLocalHost().getHostName().split("\\.")[0];
    }
    catch (final Throwable e) {
      return "AirReceiver";
    }
  }

  /**
   * Converts an array of bytes to a hexadecimal string
   *
   * @param bytes array of bytes
   * @return hexadecimal representation
   */
  private static String toHexString(final byte[] bytes) {
    final StringBuilder s = new StringBuilder();
    for(final byte b: bytes) {
      final String h = Integer.toHexString(0x100 | b);
      s.append(h.substring(h.length() - 2, h.length()).toUpperCase());
    }
    return s.toString();
  }

  /**
   * Shuts the AirReceiver down gracefully
   */
  public static void onShutdown() {
    /* Close channels */
    final ChannelGroupFuture allChannelsClosed = s_allChannels.close();

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

    /* Wait for all channels to finish closing */
    allChannelsClosed.awaitUninterruptibly();
   
    /* Stop the ExecutorService */
    ExecutorService.shutdown();

    /* Release the OrderedMemoryAwareThreadPoolExecutor */
    ChannelExecutionHandler.releaseExternalResources();
  }

    public static void main(final String[] args) throws Exception {
      /* Make sure AirReceiver shuts down gracefully */
      Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
      @Override
      public void run() {
        onShutdown();
      }
      }));

      /* Create about dialog */
      final Dialog aboutDialog = new Dialog((Dialog)null);
      final GridBagLayout aboutLayout = new GridBagLayout();
      aboutDialog.setLayout(aboutLayout);
      aboutDialog.setVisible(false);
      aboutDialog.setTitle("About AirReceiver");
      aboutDialog.setResizable(false);
      {
        /* Message */
        final TextArea title = new TextArea(AboutMessage.split("\n").length + 1, 64);
        title.setText(AboutMessage);
        title.setEditable(false);
        final GridBagConstraints titleConstraints = new GridBagConstraints();
        titleConstraints.gridx = 1;
        titleConstraints.gridy = 1;
        titleConstraints.fill = GridBagConstraints.HORIZONTAL;
        titleConstraints.insets = new Insets(0,0,0,0);
        aboutLayout.setConstraints(title, titleConstraints);
        aboutDialog.add(title);
      }
      {
        /* Done button */
        final Button aboutDoneButton = new Button("Done");
        aboutDoneButton.addActionListener(new ActionListener() {
        @Override public void actionPerformed(final ActionEvent evt) {
          aboutDialog.setVisible(false);
        }
        });
        final GridBagConstraints aboutDoneConstraints = new GridBagConstraints();
        aboutDoneConstraints.gridx = 1;
        aboutDoneConstraints.gridy = 2;
        aboutDoneConstraints.anchor = GridBagConstraints.PAGE_END;
        aboutDoneConstraints.fill = GridBagConstraints.NONE;
        aboutDoneConstraints.insets = new Insets(0,0,0,0);
        aboutLayout.setConstraints(aboutDoneButton, aboutDoneConstraints);
        aboutDialog.add(aboutDoneButton);
      }
      aboutDialog.setVisible(false);
      aboutDialog.setLocationByPlatform(true);
      aboutDialog.pack();

      /* Create tray icon */
    final URL trayIconUrl = AirReceiver.class.getClassLoader().getResource("icon_32.png");
    final TrayIcon trayIcon = new TrayIcon((new ImageIcon(trayIconUrl, "AirReceiver").getImage()));
    trayIcon.setToolTip("AirReceiver");
    trayIcon.setImageAutoSize(true);
    final PopupMenu popupMenu = new PopupMenu();
    final MenuItem aboutMenuItem = new MenuItem("About");
    aboutMenuItem.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent evt) {
          aboutDialog.setLocationByPlatform(true);
          aboutDialog.setVisible(true);
      }
    });
    popupMenu.add(aboutMenuItem);
    final MenuItem exitMenuItem = new MenuItem("Quit");
    exitMenuItem.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent evt) {
        onShutdown();
        System.exit(0);
      }
    });
    popupMenu.add(exitMenuItem);
    trayIcon.setPopupMenu(popupMenu);
    SystemTray.getSystemTray().add(trayIcon);

        /* 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);
    s_allChannels.add(airTunesRtspBootstrap.bind(new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), AirtunesServiceRTSPPort)));
        s_logger.info("Launched RTSP service on port " + AirtunesServiceRTSPPort);

      /* Create mDNS responders. */
        synchronized(s_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");
              s_jmDNSInstances.add(jmDNS);

                /* Publish RAOP service */
                final ServiceInfo airTunesServiceInfo = ServiceInfo.create(
                AirtunesServiceType,
                HardwareAddressString + "@" + HostName + " (" + iface.getName() + ")",
                AirtunesServiceRTSPPort,
                0 /* weight */, 0 /* priority */,
                AirtunesServiceProperties
              );
                jmDNS.registerService(airTunesServiceInfo);
            s_logger.info("Registered AirTunes service '" + airTunesServiceInfo.getName() + "' on " + addr);
          }
          catch (final Throwable e) {
            s_logger.log(Level.SEVERE, "Failed to publish service on " + addr, e);
          }
          }
        }
        }
    }
}
TOP

Related Classes of org.phlo.AirReceiver.AirReceiver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.