com.entertailion.java.fling.FlingFrame.java Source code

Java tutorial

Introduction

Here is the source code for com.entertailion.java.fling.FlingFrame.java

Source

/*
 * Copyright 2013 ENTERTAILION LLC
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * <http://www.apache.org/licenses/LICENSE-2.0>
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */
package com.entertailion.java.fling;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.URL;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.TimeZone;

import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicSliderUI;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import uk.co.caprica.vlcj.player.MediaPlayer;
import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;

/**
 * Main window frame for the Fling application
 * 
 * @author leon_nicholls
 * 
 */
public class FlingFrame extends JFrame
        implements ActionListener, BroadcastDiscoveryHandler, ChangeListener, WindowListener {
    private static final String LOG_TAG = "FlingFrame";

    // https://www.gstatic.com/cv/receiver.html?${POST_DATA}
    public static final String CHROMECAST = "ChromeCast";
    // TODO Add your own app id here
    private static final String APP_ID = "YOUR_APP_ID_HERE";

    private static final String HEADER_APPLICATION_URL = "Application-URL";
    private static final String CHROME_CAST_MODEL_NAME = "Eureka Dongle";
    private static final String TRANSCODING_EXTENSIONS = "wmv,avi,mkv,mpg,mpeg,flv,3gp,ogm";
    private static final String TRANSCODING_PARAMETERS = "vcodec=VP80,vb=1000,vfilter=canvas{width=640,height=360},acodec=vorb,ab=128,channels=2,samplerate=44100,threads=2";
    private static final String PROPERTY_TRANSCODING_EXTENSIONS = "transcoding.extensions";
    private static final String PROPERTY_TRANSCODING_PARAMETERS = "transcoding.parameters";
    private static final String PROPERTY_MANUAL_SERVERS = "manual.servers";
    private int port = EmbeddedServer.HTTP_PORT;
    private List<DialServer> servers = new ArrayList<DialServer>();
    private List<DialServer> manualServers = new ArrayList<DialServer>();
    private JComboBox deviceList;
    private JDialog progressDialog;
    private JButton refreshButton, playButton, pauseButton, stopButton, settingsButton;
    private JLabel label;
    private JSlider scrubber;
    private JSlider volume;
    private ResourceBundle resourceBundle;
    private EmbeddedServer embeddedServer;

    private BroadcastDiscoveryClient broadcastClient;
    private Thread broadcastClientThread;
    private TrackedDialServers trackedServers = new TrackedDialServers();
    private RampClient rampClient;

    private MediaPlayerFactory mediaPlayerFactory;
    private MediaPlayer mediaPlayer;

    private String transcodingParameterValues = TRANSCODING_PARAMETERS;
    private String transcodingExtensionValues = TRANSCODING_EXTENSIONS;

    private DialServer selectedDialServer;

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");

    private int duration;
    private boolean playbackValueIsAdjusting;
    private boolean isTranscoding;
    private String appId;

    public FlingFrame(String appId) {
        super();
        this.appId = appId;
        rampClient = new RampClient(this);

        addWindowListener(this);

        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

        Locale locale = Locale.getDefault();
        resourceBundle = ResourceBundle.getBundle("com/entertailion/java/fling/resources/resources", locale);
        setTitle(MessageFormat.format(resourceBundle.getString("fling.title"), Fling.VERSION));

        JPanel listPane = new JPanel();
        // show list of ChromeCast devices detected on the local network
        listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
        JPanel devicePane = new JPanel();
        devicePane.setLayout(new BoxLayout(devicePane, BoxLayout.LINE_AXIS));
        deviceList = new JComboBox();
        deviceList.addActionListener(this);
        devicePane.add(deviceList);
        URL url = ClassLoader.getSystemResource("com/entertailion/java/fling/resources/refresh.png");
        ImageIcon icon = new ImageIcon(url, resourceBundle.getString("button.refresh"));
        refreshButton = new JButton(icon);
        refreshButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                // refresh the list of devices
                if (deviceList.getItemCount() > 0) {
                    deviceList.setSelectedIndex(0);
                }
                discoverDevices();
            }
        });
        refreshButton.setToolTipText(resourceBundle.getString("button.refresh"));
        devicePane.add(refreshButton);
        url = ClassLoader.getSystemResource("com/entertailion/java/fling/resources/settings.png");
        icon = new ImageIcon(url, resourceBundle.getString("settings.title"));
        settingsButton = new JButton(icon);
        settingsButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                JTextField transcodingExtensions = new JTextField(50);
                transcodingExtensions.setText(transcodingExtensionValues);
                JTextField transcodingParameters = new JTextField(50);
                transcodingParameters.setText(transcodingParameterValues);

                JPanel myPanel = new JPanel(new BorderLayout());
                JPanel labelPanel = new JPanel(new GridLayout(3, 1));
                JPanel fieldPanel = new JPanel(new GridLayout(3, 1));
                myPanel.add(labelPanel, BorderLayout.WEST);
                myPanel.add(fieldPanel, BorderLayout.CENTER);
                labelPanel.add(new JLabel(resourceBundle.getString("transcoding.extensions"), JLabel.RIGHT));
                fieldPanel.add(transcodingExtensions);
                labelPanel.add(new JLabel(resourceBundle.getString("transcoding.parameters"), JLabel.RIGHT));
                fieldPanel.add(transcodingParameters);
                labelPanel.add(new JLabel(resourceBundle.getString("device.manual"), JLabel.RIGHT));
                JPanel devicePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
                final JComboBox manualDeviceList = new JComboBox();
                if (manualServers.size() == 0) {
                    manualDeviceList.setVisible(false);
                } else {
                    for (DialServer dialServer : manualServers) {
                        manualDeviceList.addItem(dialServer);
                    }
                }
                devicePanel.add(manualDeviceList);
                JButton addButton = new JButton(resourceBundle.getString("device.manual.add"));
                addButton.setToolTipText(resourceBundle.getString("device.manual.add.tooltip"));
                addButton.addActionListener(new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        JTextField name = new JTextField();
                        JTextField ipAddress = new JTextField();
                        Object[] message = { resourceBundle.getString("device.manual.name") + ":", name,
                                resourceBundle.getString("device.manual.ipaddress") + ":", ipAddress };

                        int option = JOptionPane.showConfirmDialog(null, message,
                                resourceBundle.getString("device.manual"), JOptionPane.OK_CANCEL_OPTION);
                        if (option == JOptionPane.OK_OPTION) {
                            try {
                                manualServers.add(
                                        new DialServer(name.getText(), InetAddress.getByName(ipAddress.getText())));

                                Object selected = deviceList.getSelectedItem();
                                int selectedIndex = deviceList.getSelectedIndex();
                                deviceList.removeAllItems();
                                deviceList.addItem(resourceBundle.getString("devices.select"));
                                for (DialServer dialServer : servers) {
                                    deviceList.addItem(dialServer);
                                }
                                for (DialServer dialServer : manualServers) {
                                    deviceList.addItem(dialServer);
                                }
                                deviceList.invalidate();
                                if (selectedIndex > 0) {
                                    deviceList.setSelectedItem(selected);
                                } else {
                                    if (deviceList.getItemCount() == 2) {
                                        // Automatically select single device
                                        deviceList.setSelectedIndex(1);
                                    }
                                }

                                manualDeviceList.removeAllItems();
                                for (DialServer dialServer : manualServers) {
                                    manualDeviceList.addItem(dialServer);
                                }
                                manualDeviceList.setVisible(true);
                                storeProperties();
                            } catch (UnknownHostException e1) {
                                Log.e(LOG_TAG, "manual IP address", e1);

                                JOptionPane.showMessageDialog(FlingFrame.this,
                                        resourceBundle.getString("device.manual.invalidip"));
                            }
                        }
                    }
                });
                devicePanel.add(addButton);
                JButton removeButton = new JButton(resourceBundle.getString("device.manual.remove"));
                removeButton.setToolTipText(resourceBundle.getString("device.manual.remove.tooltip"));
                removeButton.addActionListener(new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        Object selected = manualDeviceList.getSelectedItem();
                        manualDeviceList.removeItem(selected);
                        if (manualDeviceList.getItemCount() == 0) {
                            manualDeviceList.setVisible(false);
                        }
                        deviceList.removeItem(selected);
                        deviceList.invalidate();
                        manualServers.remove(selected);
                        storeProperties();
                    }
                });
                devicePanel.add(removeButton);
                fieldPanel.add(devicePanel);
                int result = JOptionPane.showConfirmDialog(FlingFrame.this, myPanel,
                        resourceBundle.getString("settings.title"), JOptionPane.OK_CANCEL_OPTION);
                if (result == JOptionPane.OK_OPTION) {
                    transcodingParameterValues = transcodingParameters.getText();
                    transcodingExtensionValues = transcodingExtensions.getText();
                    storeProperties();
                }
            }
        });
        settingsButton.setToolTipText(resourceBundle.getString("settings.title"));
        devicePane.add(settingsButton);
        listPane.add(devicePane);

        // TODO
        volume = new JSlider(JSlider.VERTICAL, 0, 100, 0);
        volume.setUI(new MySliderUI(volume));
        volume.setMajorTickSpacing(25);
        // volume.setMinorTickSpacing(5);
        volume.setPaintTicks(true);
        volume.setEnabled(true);
        volume.setValue(100);
        volume.setToolTipText(resourceBundle.getString("volume.title"));
        volume.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                JSlider source = (JSlider) e.getSource();
                if (!source.getValueIsAdjusting()) {
                    int position = (int) source.getValue();
                    rampClient.volume(position / 100.0f);
                }
            }

        });
        JPanel centerPanel = new JPanel(new BorderLayout());
        // centerPanel.add(volume, BorderLayout.WEST);

        centerPanel.add(DragHereIcon.makeUI(this), BorderLayout.CENTER);
        listPane.add(centerPanel);

        scrubber = new JSlider(JSlider.HORIZONTAL, 0, 100, 0);
        scrubber.addChangeListener(this);
        scrubber.setMajorTickSpacing(25);
        scrubber.setMinorTickSpacing(5);
        scrubber.setPaintTicks(true);
        scrubber.setEnabled(false);
        listPane.add(scrubber);

        // panel of playback buttons
        JPanel buttonPane = new JPanel();
        buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
        label = new JLabel("00:00:00");
        buttonPane.add(label);
        url = ClassLoader.getSystemResource("com/entertailion/java/fling/resources/play.png");
        icon = new ImageIcon(url, resourceBundle.getString("button.play"));
        playButton = new JButton(icon);
        playButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                rampClient.play();
            }
        });
        buttonPane.add(playButton);
        url = ClassLoader.getSystemResource("com/entertailion/java/fling/resources/pause.png");
        icon = new ImageIcon(url, resourceBundle.getString("button.pause"));
        pauseButton = new JButton(icon);
        pauseButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                rampClient.pause();
            }
        });
        buttonPane.add(pauseButton);
        url = ClassLoader.getSystemResource("com/entertailion/java/fling/resources/stop.png");
        icon = new ImageIcon(url, resourceBundle.getString("button.stop"));
        stopButton = new JButton(icon);
        stopButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                rampClient.stop();
                setDuration(0);
                scrubber.setValue(0);
                scrubber.setEnabled(false);
            }
        });
        buttonPane.add(stopButton);
        listPane.add(buttonPane);
        getContentPane().add(listPane);

        createProgressDialog();
        startWebserver();
        discoverDevices();
    }

    // http://stackoverflow.com/questions/6992633/painting-the-slider-icon-of-jslider
    private static class MySliderUI extends BasicSliderUI {

        private Font font = new Font(Font.SERIF, Font.PLAIN, 12);

        public MySliderUI(JSlider slider) {
            super(slider);
        }

        @Override
        public void paintTrack(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            Rectangle t = trackRect;
            g.setColor(Color.darkGray);
            g.drawRect(t.x, t.y, t.width, t.height);

            if (g instanceof Graphics2D) {
                Graphics2D g2 = (Graphics2D) g;
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setFont(font);
                int w2 = slider.getFontMetrics(font).stringWidth("Volume") / 2;
                g2.drawString("Volume", t.width / 2 - w2, t.height);
            }
        }

        @Override
        public void paintThumb(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            Rectangle t = thumbRect;
            g2d.setColor(Color.black);
            int tw2 = t.width / 2;
            g2d.drawLine(t.x, t.y, t.x + t.width - 1, t.y);
            g2d.drawLine(t.x, t.y, t.x + tw2, t.y + t.height);
            g2d.drawLine(t.x + t.width - 1, t.y, t.x + tw2, t.y + t.height);
        }
    }

    public void stateChanged(ChangeEvent e) {
        JSlider source = (JSlider) e.getSource();
        if (!source.getValueIsAdjusting() && !playbackValueIsAdjusting) {
            int position = (int) source.getValue();
            if (position == 0) {
                rampClient.play(0);
            } else {
                rampClient.play((int) (position / 100.0f * duration));
            }
        }
    }

    /**
     * Start a web server to serve the videos to the media player on the
     * ChromeCast device
     */
    private void startWebserver() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                boolean started = false;
                while (!started) {
                    try {
                        embeddedServer = new EmbeddedServer(port);
                        Log.d(LOG_TAG, "Started web server on port " + port);
                        started = true;
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                        port++;
                    } catch (Exception ex) {
                        break;
                    }
                }
            }

        }).start();
    }

    /**
     * Discover ChromeCast devices on the local network
     * 
     */
    private void discoverDevices() {
        Properties properties = loadProperties();

        broadcastClient = new BroadcastDiscoveryClient(this);
        broadcastClientThread = new Thread(broadcastClient);

        deviceList.removeAllItems();
        // Prompt selection option
        deviceList.addItem(resourceBundle.getString("devices.select"));

        // discovering devices can take time, so do it in a thread
        new Thread(new Runnable() {
            public void run() {
                try {
                    showProgressDialog(resourceBundle.getString("progress.discoveringDevices"));
                    broadcastClientThread.start();

                    // wait a while...
                    // TODO do this better
                    Thread.sleep(BroadcastDiscoveryClient.PROBE_INTERVAL_MS - 1);

                    broadcastClient.stop();

                    hideProgressDialog();

                    Log.d(LOG_TAG, "size=" + trackedServers.size());
                    for (DialServer dialServer : trackedServers) {
                        deviceList.addItem(dialServer);
                        servers.add(dialServer);
                    }

                    // Now add user's manual servers
                    for (DialServer dialServer : manualServers) {
                        deviceList.addItem(dialServer);
                    }
                    deviceList.invalidate();

                    if (deviceList.getItemCount() == 1) {
                        JOptionPane.showMessageDialog(FlingFrame.this, resourceBundle.getString("device.notfound"));
                    } else if (deviceList.getItemCount() == 2) {
                        // Automatically select single device
                        deviceList.setSelectedIndex(1);
                    }

                } catch (InterruptedException e) {
                    Log.e(LOG_TAG, "discoverDevices", e);
                }
            }
        }).start();
    }

    public void onBroadcastFound(final BroadcastAdvertisement advert) {
        if (advert.getLocation() != null) {
            new Thread(new Runnable() {
                public void run() {
                    Log.d(LOG_TAG, "location=" + advert.getLocation());
                    HttpResponse response = new HttpRequestHelper().sendHttpGet(advert.getLocation());
                    if (response != null) {
                        String appsUrl = null;
                        Header header = response.getLastHeader(HEADER_APPLICATION_URL);
                        if (header != null) {
                            appsUrl = header.getValue();
                            if (!appsUrl.endsWith("/")) {
                                appsUrl = appsUrl + "/";
                            }
                            Log.d(LOG_TAG, "appsUrl=" + appsUrl);
                        }
                        try {
                            InputStream inputStream = response.getEntity().getContent();
                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

                            InputSource inStream = new org.xml.sax.InputSource();
                            inStream.setCharacterStream(reader);
                            SAXParserFactory spf = SAXParserFactory.newInstance();
                            SAXParser sp = spf.newSAXParser();
                            XMLReader xr = sp.getXMLReader();
                            BroadcastHandler broadcastHandler = new BroadcastHandler();
                            xr.setContentHandler(broadcastHandler);
                            xr.parse(inStream);
                            Log.d(LOG_TAG, "modelName=" + broadcastHandler.getDialServer().getModelName());
                            // Only handle ChromeCast devices; not other DIAL
                            // devices like ChromeCast devices
                            if (broadcastHandler.getDialServer().getModelName().equals(CHROME_CAST_MODEL_NAME)) {
                                Log.d(LOG_TAG,
                                        "ChromeCast device found: " + advert.getIpAddress().getHostAddress());
                                DialServer dialServer = new DialServer(advert.getLocation(), advert.getIpAddress(),
                                        advert.getPort(), appsUrl,
                                        broadcastHandler.getDialServer().getFriendlyName(),
                                        broadcastHandler.getDialServer().getUuid(),
                                        broadcastHandler.getDialServer().getManufacturer(),
                                        broadcastHandler.getDialServer().getModelName());
                                trackedServers.add(dialServer);
                            }
                        } catch (Exception e) {
                            Log.e(LOG_TAG, "parse device description", e);
                        }
                    }
                }
            }).start();
        }
    }

    private InterfaceAddress getPreferredInetAddress(String prefix) {
        InterfaceAddress selectedInterfaceAddress = null;
        try {
            Enumeration<NetworkInterface> list = NetworkInterface.getNetworkInterfaces();

            while (list.hasMoreElements()) {
                NetworkInterface iface = list.nextElement();
                if (iface == null)
                    continue;
                Log.d(LOG_TAG, "interface=" + iface.getName());
                Iterator<InterfaceAddress> it = iface.getInterfaceAddresses().iterator();
                while (it.hasNext()) {
                    InterfaceAddress interfaceAddress = it.next();
                    if (interfaceAddress == null)
                        continue;
                    InetAddress address = interfaceAddress.getAddress();
                    Log.d(LOG_TAG, "address=" + address);
                    if (address instanceof Inet4Address) {
                        // Only pick an interface that is likely to be on the
                        // same subnet as the selected ChromeCast device
                        if (address.getHostAddress().toString().startsWith(prefix)) {
                            return interfaceAddress;
                        }
                    }
                }
            }
        } catch (Exception ex) {
        }
        return selectedInterfaceAddress;
    }

    /**
     * Get the network address.
     * 
     * @return
     */
    public Inet4Address getNetworAddress() {
        Inet4Address selectedInetAddress = null;
        try {
            InterfaceAddress interfaceAddress = null;
            if (selectedDialServer != null) {
                String address = selectedDialServer.getIpAddress().getHostAddress();
                String prefix = address.substring(0, address.indexOf('.') + 1);
                Log.d(LOG_TAG, "prefix=" + prefix);
                interfaceAddress = getPreferredInetAddress(prefix);
            } else {
                InterfaceAddress oneNineTwoInetAddress = getPreferredInetAddress("192.");
                if (oneNineTwoInetAddress != null) {
                    interfaceAddress = oneNineTwoInetAddress;
                } else {
                    InterfaceAddress oneSevenTwoInetAddress = getPreferredInetAddress("172.");
                    if (oneSevenTwoInetAddress != null) {
                        interfaceAddress = oneSevenTwoInetAddress;
                    } else {
                        interfaceAddress = getPreferredInetAddress("10.");
                    }
                }
            }
            if (interfaceAddress != null) {
                InetAddress networkAddress = interfaceAddress.getAddress();
                Log.d(LOG_TAG, "networkAddress=" + networkAddress);
                if (networkAddress != null) {
                    return (Inet4Address) networkAddress;
                }
            }
        } catch (Exception ex) {
        }

        return selectedInetAddress;
    }

    /**
     * Create a progress indicator
     */
    private void createProgressDialog() {
        progressDialog = new JDialog(this, resourceBundle.getString("progress.title"), true);
        JProgressBar progressBar = new JProgressBar(0, 100);
        progressBar.setStringPainted(true);
        progressBar.setIndeterminate(true);
        progressDialog.add(BorderLayout.CENTER, progressBar);
        progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
        progressDialog.setSize(300, 75);
        progressDialog.setLocationRelativeTo(this);
    }

    /**
     * Show the progress indicator with a title message
     * 
     * @param message
     */
    private void showProgressDialog(final String message) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                progressDialog.setLocationRelativeTo(FlingFrame.this);
                progressDialog.setTitle(message);
                progressDialog.setVisible(true);
            }
        });
    }

    /**
     * Hide the progress indicator
     */
    private void hideProgressDialog() {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                progressDialog.setVisible(false);
            }
        });
    }

    /**
     * Event handler for device dropdown list selection
     * 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
        JComboBox cb = (JComboBox) e.getSource();
        int pos = cb.getSelectedIndex();
        // when device is selected, attempt to connect
        if (servers != null && pos > 0) {
            selectedDialServer = (DialServer) cb.getSelectedItem();
        }
    }

    /**
     * Send a uri to the ChromeCast device
     * 
     * @param keycode
     */
    protected void sendMediaUrl(String file) {
        if (selectedDialServer == null) {
            JOptionPane.showMessageDialog(this, resourceBundle.getString("device.select"));
            return;
        }
        isTranscoding = false;
        Log.d(LOG_TAG, "sendMediaUrl=" + file);
        if (file != null) {
            duration = 0;
            boolean found = false;
            String[] extensions = transcodingExtensionValues.split(",");
            for (String extension : extensions) {
                if (file.endsWith(extension.trim())) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                try {
                    int pos = file.lastIndexOf('.');
                    String extension = "";
                    if (pos > -1) {
                        extension = file.substring(pos);
                    }
                    Inet4Address address = getNetworAddress();
                    if (address != null) {
                        final String url = "http://" + address.getHostAddress() + ":" + port + "/video" + extension;
                        if (!rampClient.isClosed()) {
                            rampClient.stop();
                        }
                        rampClient.launchApp(appId == null ? APP_ID : appId, selectedDialServer);
                        // wait for socket to be ready...
                        new Thread(new Runnable() {
                            public void run() {
                                while (!rampClient.isStarted() && !rampClient.isClosed()) {
                                    try {
                                        // make less than 3 second ping time
                                        Thread.sleep(500);
                                    } catch (InterruptedException e) {
                                    }
                                }
                                if (!rampClient.isClosed()) {
                                    try {
                                        Thread.sleep(500);
                                    } catch (InterruptedException e) {
                                    }
                                    rampClient.load(url);
                                }
                            }
                        }).start();
                    } else {
                        Log.d(LOG_TAG, "could not find a network interface");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                vlcTranscode(file);
            }

            // TODO
            if (!volume.getValueIsAdjusting()) {
                int position = (int) volume.getValue();
                // rampClient.volume(position / 100.0f);
            }
        }
    }

    protected void vlcTranscode(final String file) {
        // Transcoding does not support jumps
        isTranscoding = true;

        // http://caprica.github.io/vlcj/javadoc/2.1.0/index.html
        try {
            // clean up previous session
            if (mediaPlayer != null) {
                mediaPlayer.release();
            }
            if (mediaPlayerFactory != null) {
                mediaPlayerFactory.release();
            }
            mediaPlayerFactory = new MediaPlayerFactory();
            mediaPlayer = mediaPlayerFactory.newHeadlessMediaPlayer();
            // Add a component to be notified of player events
            mediaPlayer.addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
                public void opening(MediaPlayer mediaPlayer) {
                    Log.d(LOG_TAG, "VLC Transcoding: Opening");
                }

                public void buffering(MediaPlayer mediaPlayer, float newCache) {
                    Log.d(LOG_TAG, "VLC Transcoding: Buffering");
                }

                public void playing(MediaPlayer mediaPlayer) {
                    Log.d(LOG_TAG, "VLC Transcoding: Playing");

                    setDuration((int) (mediaPlayer.getLength() / 1000.0f));
                    Log.d(LOG_TAG, "duration=" + duration);
                }

                public void paused(MediaPlayer mediaPlayer) {
                    Log.d(LOG_TAG, "VLC Transcoding: Paused");
                }

                public void stopped(MediaPlayer mediaPlayer) {
                    Log.d(LOG_TAG, "VLC Transcoding: Stopped");
                }

                public void finished(MediaPlayer mediaPlayer) {
                    Log.d(LOG_TAG, "VLC Transcoding: Finished");
                }

                public void error(MediaPlayer mediaPlayer) {
                    Log.d(LOG_TAG, "VLC Transcoding: Error");
                }

                public void videoOutput(MediaPlayer mediaPlayer, int newCount) {
                    Log.d(LOG_TAG, "VLC Transcoding: VideoOutput");
                }
            });

            // Find a port for VLC HTTP server
            boolean started = false;
            int vlcPort = port + 1;
            while (!started) {
                try {
                    ServerSocket serverSocket = new ServerSocket(vlcPort);
                    Log.d(LOG_TAG, "Available port for VLC: " + vlcPort);
                    started = true;
                    serverSocket.close();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                    vlcPort++;
                } catch (Exception ex) {
                    break;
                }
            }

            if (!rampClient.isClosed()) {
                rampClient.stop();
            }
            Inet4Address address = getNetworAddress();
            if (address != null) {
                // Play a particular item, with options if necessary
                final String options[] = { ":sout=#transcode{" + transcodingParameterValues
                        + "}:http{mux=webm,dst=:" + vlcPort + "/cast.webm}", ":sout-keep" };
                // http://192.168.0.8:8087/cast.webm
                final String url = "http://" + address.getHostAddress() + ":" + vlcPort + "/cast.webm";
                Log.d(LOG_TAG, "url=" + url);
                if (true || isChromeCast()) {
                    rampClient.launchApp(appId == null ? APP_ID : appId, selectedDialServer);
                    // wait for socket to be ready...
                    new Thread(new Runnable() {
                        public void run() {
                            while (!rampClient.isStarted() && !rampClient.isClosed()) {
                                try {
                                    Thread.sleep(500);
                                } catch (InterruptedException e) {
                                }
                            }
                            if (!rampClient.isClosed()) {
                                mediaPlayer.playMedia(file, options);
                                rampClient.load(url);
                            }
                        }
                    }).start();
                } else {
                    rampClient.load(url);
                }
            } else {
                Log.d(LOG_TAG, "could not find a network interface");
            }
        } catch (Throwable e) {
            Log.e(LOG_TAG, "vlcTranscode: " + file, e);
        }
    }

    // Store user settings
    private Properties loadProperties() {
        Properties prop = new Properties();
        try {
            prop.load(new FileInputStream("config.properties"));
            transcodingParameterValues = prop.getProperty(PROPERTY_TRANSCODING_PARAMETERS);
            transcodingExtensionValues = prop.getProperty(PROPERTY_TRANSCODING_EXTENSIONS);
            String manual = prop.getProperty(PROPERTY_MANUAL_SERVERS);
            if (manual != null) {
                String[] parts = manual.split(":");
                for (int i = 0; i < parts.length / 2; i++) {
                    manualServers.add(new DialServer(parts[i * 2], InetAddress.getByName(parts[i * 2 + 1])));
                }
            }
        } catch (Exception ex) {
        }
        if (transcodingParameterValues == null) {
            transcodingParameterValues = TRANSCODING_PARAMETERS;
        }
        if (transcodingExtensionValues == null) {
            transcodingExtensionValues = TRANSCODING_EXTENSIONS;
        }
        return prop;
    }

    // Restore user settings
    private void storeProperties() {
        Properties prop = new Properties();
        try {
            prop.setProperty(PROPERTY_TRANSCODING_PARAMETERS, transcodingParameterValues);
            prop.setProperty(PROPERTY_TRANSCODING_EXTENSIONS, transcodingExtensionValues);
            String manual = "";
            for (DialServer dialServer : manualServers) {
                if (manual.length() > 0) {
                    manual = manual + ":";
                }
                manual = manual + dialServer.getFriendlyName() + ":" + dialServer.getIpAddress().getHostAddress();
            }
            prop.setProperty(PROPERTY_MANUAL_SERVERS, manual);
            prop.store(new FileOutputStream("config.properties"), null);
        } catch (Exception ex) {
        }
    }

    public void updateTime(int time) {
        label.setText(simpleDateFormat.format(new Date(time * 1000)));
        if (duration > 0 && !scrubber.getValueIsAdjusting()) {
            playbackValueIsAdjusting = true;
            scrubber.setValue((int) ((time * 1.0f) / duration * 100));
            playbackValueIsAdjusting = false;
        }
    }

    // Current video duration in seconds
    public int getDuration() {
        return duration;
    }

    // Display the current duration in the slider
    public void setDuration(int duration) {
        this.duration = duration;
        if (duration >= 0) {
            Hashtable labelTable = new Hashtable();
            labelTable.put(new Integer(0), new JLabel("0"));
            labelTable.put(new Integer(100), new JLabel(simpleDateFormat.format(new Date(duration * 1000))));
            scrubber.setLabelTable(labelTable);
            scrubber.setPaintLabels(true);
            if (!isTranscoding) {
                scrubber.setEnabled(true);
            } else {
                scrubber.setEnabled(false);
            }
        }
    }

    @Override
    public void windowActivated(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowClosed(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowClosing(WindowEvent arg0) {
        // TODO Auto-generated method stub
        if (rampClient != null) {
            rampClient.stop();
        }
    }

    @Override
    public void windowDeactivated(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowDeiconified(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowIconified(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowOpened(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    public boolean isChromeCast() {
        String id = appId == null ? APP_ID : appId;
        return id.equals(FlingFrame.CHROMECAST);
    }

}