Package net.java.sip.communicator.impl.neomedia

Source Code of net.java.sip.communicator.impl.neomedia.MediaConfigurationImpl

/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.neomedia;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.concurrent.*;

import javax.media.*;
import javax.media.MediaException;
import javax.media.protocol.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.plugin.desktoputil.TransparentPanel;
import net.java.sip.communicator.util.*;

import org.jitsi.impl.neomedia.*;
import org.jitsi.impl.neomedia.device.*;
import org.jitsi.service.audionotifier.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.codec.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.neomedia.event.*;
import org.jitsi.service.resources.*;
import org.jitsi.util.swing.*;

/**
*
* @author Lyubomir Marinov
* @author Damian Minkov
* @author Yana Stamcheva
* @author Boris Grozev
* @author Vincent Lucas
*/
public class MediaConfigurationImpl
    implements ActionListener,
               MediaConfigurationService
{
    /**
     * Creates a new listener to combo box and affect changes to the audio level
     * indicator. The level indicator is updated via a thread in order to avoid
     * deadlock of the user interface.
     */
    private class AudioLevelListenerThread
        implements ActionListener,
                   HierarchyListener
    {
        /**
         * Listener to update the audio level indicator.
         */
        private final SimpleAudioLevelListener audioLevelListener
            = new SimpleAudioLevelListener()
            {
                public void audioLevelChanged(int level)
                {
                    soundLevelIndicator.updateSoundLevel(level);
                }
            };

        /**
         * The audio system used to get and set the sound devices.
         */
        private AudioSystem audioSystem;

        /**
         * The combo box used to select the device the user wants to use.
         */
        private JComboBox comboBox;

        /**
         * The current capture device.
         */
        private AudioMediaDeviceSession deviceSession;

        /**
         * The new device chosen by the user and that we need to initialize as
         * the new capture device.
         */
        private AudioMediaDeviceSession deviceSessionToSet;

        /**
         * The indicator which determines whether
         * {@link #setDeviceSession(AudioMediaDeviceSession)} is to be invoked
         * when {@link #deviceSessionToSet} is <tt>null</tt>.
         */
        private boolean deviceSessionToSetIsNull;

        /**
         * The <tt>ExecutorService</tt> which is to asynchronously invoke
         * {@link #setDeviceSession(AudioMediaDeviceSession)} with
         * {@link #deviceSessionToSet}.
         */
        private final ExecutorService setDeviceSessionExecutor
            = Executors.newSingleThreadExecutor();

        private final Runnable setDeviceSessionTask
            = new Runnable()
            {
                public void run()
                {
                    AudioMediaDeviceSession deviceSession = null;
                    boolean deviceSessionIsNull = false;

                    synchronized (AudioLevelListenerThread.this)
                    {
                        if ((deviceSessionToSet != null)
                                || deviceSessionToSetIsNull)
                        {
                            /*
                             * Invoke #setDeviceSession(AudioMediaDeviceSession)
                             * outside the synchronized block to avoid a GUI
                             * deadlock.
                             */
                            deviceSession = deviceSessionToSet;
                            deviceSessionIsNull = deviceSessionToSetIsNull;
                            deviceSessionToSet = null;
                            deviceSessionToSetIsNull = false;
                        }
                    }

                    if ((deviceSession != null) || deviceSessionIsNull)
                    {
                        /*
                         * XXX The method blocks on Mac OS X for Bluetooth
                         * devices which are paired but disconnected.
                         */
                        setDeviceSession(deviceSession);
                    }
                }
            };

        /**
         * The sound level indicator used to show the effectiveness of the
         * capture device.
         */
        private SoundLevelIndicator soundLevelIndicator;

        /**
         *  Provides an handler which reads the stream into the
         *  "transferHandlerBuffer".
         */
        private final BufferTransferHandler transferHandler
            = new BufferTransferHandler()
            {
                public void transferData(PushBufferStream stream)
                {
                    try
                    {
                        stream.read(transferHandlerBuffer);
                    }
                    catch (IOException ioe)
                    {
                    }
                }
            };

        /**
         * The buffer used for reading the capture device.
         */
        private final Buffer transferHandlerBuffer = new Buffer();

        /**
         * Creates a new listener to combo box and affect changes to the audio
         * level indicator.
         *
         * @param audioSystem The audio system used to get and set the sound
         * devices.
         * @param comboBox The combo box used to select the device the user
         * wants to use.
         * @param soundLevelIndicator The sound level indicator used to show the
         * effectiveness of the capture device.
         */
        public AudioLevelListenerThread(
                AudioSystem audioSystem,
                JComboBox comboBox,
                SoundLevelIndicator soundLevelIndicator)
        {
            init(audioSystem, comboBox, soundLevelIndicator);
        }

        /**
         * Refresh combo box when the user click on it.
         *
         * @param ev The click on the  combo box.
         */
        public void actionPerformed(ActionEvent ev)
        {
            synchronized (this)
            {
                deviceSessionToSet = null;
                deviceSessionToSetIsNull = true;
                setDeviceSessionExecutor.execute(setDeviceSessionTask);
            }

            CaptureDeviceInfo cdi;

            if (comboBox == null)
            {
                cdi
                    = soundLevelIndicator.isShowing()
                        ? audioSystem.getDevice(AudioSystem.CAPTURE_INDEX)
                        : null;
            }
            else
            {
                Object selectedItem
                    = soundLevelIndicator.isShowing()
                        ? comboBox.getSelectedItem()
                        : null;

                cdi
                    = (selectedItem
                            instanceof
                                DeviceConfigurationComboBoxModel.CaptureDevice)
                        ? ((DeviceConfigurationComboBoxModel.CaptureDevice)
                                selectedItem)
                            .info
                        : null;
            }

            if (cdi != null)
            {
                for (MediaDevice md: mediaService.getDevices(
                            MediaType.AUDIO,
                            MediaUseCase.ANY))
                {
                    if (md instanceof AudioMediaDeviceImpl)
                    {
                        AudioMediaDeviceImpl amd = (AudioMediaDeviceImpl) md;

                        if (cdi.equals(amd.getCaptureDeviceInfo()))
                        {
                            try
                            {
                                MediaDeviceSession deviceSession
                                    = amd.createSession();
                                boolean deviceSessionIsSet = false;

                                try
                                {
                                    if (deviceSession instanceof
                                            AudioMediaDeviceSession)
                                    {
                                        synchronized (this)
                                        {
                                            deviceSessionToSet
                                                = (AudioMediaDeviceSession)
                                                    deviceSession;
                                            deviceSessionToSetIsNull
                                                = (deviceSessionToSet == null);
                                            setDeviceSessionExecutor.execute(
                                                    setDeviceSessionTask);
                                        }
                                        deviceSessionIsSet = true;
                                    }
                                }
                                finally
                                {
                                    if (!deviceSessionIsSet)
                                        deviceSession.close();
                                }
                            }
                            catch (Throwable t)
                            {
                                if (t instanceof ThreadDeath)
                                    throw (ThreadDeath) t;
                            }
                            break;
                        }
                    }
                }
            }
        }

        public void hierarchyChanged(HierarchyEvent ev)
        {
            if ((ev.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0)
            {
                SwingUtilities.invokeLater(
                        new Runnable()
                        {
                            public void run()
                            {
                                actionPerformed(null);
                            }
                        });
            }
        }

        /**
         * Creates a new listener to combo box and affect changes to the audio
         * level indicator.
         *
         * @param audioSystem The audio system used to get and set the sound
         * devices.
         * @param comboBox The combo box used to select the device the user
         * wants to use.
         * @param soundLevelIndicator The sound level indicator used to show the
         * effectiveness of the capture device.
         */
        public void init(
                AudioSystem audioSystem,
                JComboBox comboBox,
                SoundLevelIndicator soundLevelIndicator)
        {
            this.audioSystem = audioSystem;

            if (this.comboBox != comboBox)
            {
                if (this.comboBox != null)
                    this.comboBox.removeActionListener(this);
                this.comboBox = comboBox;
                if (comboBox != null)
                    comboBox.addActionListener(this);
            }

            if (this.soundLevelIndicator != soundLevelIndicator)
            {
                if (this.soundLevelIndicator != null)
                    this.soundLevelIndicator.removeHierarchyListener(this);
                this.soundLevelIndicator = soundLevelIndicator;
                if (soundLevelIndicator != null)
                    soundLevelIndicator.addHierarchyListener(this);
            }
        }

        /**
         * Sets the new capture device used by the audio level indicator.
         *
         * @param deviceSession The new capture device used by the audio level
         * indicator.
         */
        private void setDeviceSession(AudioMediaDeviceSession deviceSession)
        {
            if (this.deviceSession == deviceSession)
                return;

            if (this.deviceSession != null)
            {
                try
                {
                    this.deviceSession.close();
                }
                finally
                {
                    this.deviceSession.setLocalUserAudioLevelListener(null);
                    soundLevelIndicator.resetSoundLevel();
                }
            }

            this.deviceSession = deviceSession;

            if (deviceSession != null)
            {
                deviceSession.setContentDescriptor(
                        new ContentDescriptor(ContentDescriptor.RAW));
                deviceSession.setLocalUserAudioLevelListener(
                        audioLevelListener);

                deviceSession.start(MediaDirection.SENDONLY);

                try
                {
                    DataSource dataSource = deviceSession.getOutputDataSource();

                    dataSource.connect();

                    PushBufferStream[] streams
                        = ((PushBufferDataSource) dataSource).getStreams();

                    for (PushBufferStream stream : streams)
                        stream.setTransferHandler(transferHandler);

                    dataSource.start();
                }
                catch (Throwable t)
                {
                    if (t instanceof ThreadDeath)
                        throw (ThreadDeath) t;
                }
            }
        }
    }

    /**
     * Renders the available resolutions in the combo box.
     */
    private static class ResolutionCellRenderer
        extends DefaultListCellRenderer
    {
        /**
         * The serialization version number of the
         * <tt>ResolutionCellRenderer</tt> class. Defined to the value of
         * <tt>0</tt> because the <tt>ResolutionCellRenderer</tt> instances do
         * not have state of their own.
         */
        private static final long serialVersionUID = 0L;

        /**
         * Sets readable text describing the resolution if the selected
         * value is null we return the string "Auto".
         *
         * @param list
         * @param value
         * @param index
         * @param isSelected
         * @param cellHasFocus
         * @return Component
         */
        @Override
        public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus)
        {
            // call super to set backgrounds and fonts
            super.getListCellRendererComponent(
                    list,
                    value,
                    index,
                    isSelected,
                    cellHasFocus);

            // now just change the text
            if(value == null)
                setText("Auto");
            else if(value instanceof Dimension)
            {
                Dimension d = (Dimension)value;

                setText(((int) d.getWidth()) + "x" + ((int) d.getHeight()));
            }
            return this;
        }
    }

    /**
     * The name of the property which specifies if the audio system interface
     * is disabled.
     */
    private static final String AUDIO_SYSTEM_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.audiosystem.DISABLED";

    /**
     * Indicates if the Devices settings configuration tab
     * should be disabled, i.e. not visible to the user.
     */
    private static final String DEVICES_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.devicesconfig.DISABLED";

    /**
     * Indicates if the Audio/Video encodings configuration tab
     * should be disabled, i.e. not visible to the user.
     */
    private static final String ENCODINGS_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.encodingsconfig.DISABLED";

    /**
     * The <tt>Logger</tt> used by the <tt>MediaConfigurationServiceImpl</tt>
     * class for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(MediaConfigurationImpl.class);

    /**
     * The <tt>MediaService</tt> implementation used by
     * <tt>MediaConfigurationImpl</tt>.
     */
    private static final MediaServiceImpl mediaService
        = NeomediaActivator.getMediaServiceImpl();

    /**
     * The name of the sound file used to test the playback and the notification
     * devices.
     */
    private static final String TEST_SOUND_FILENAME_PROP
        = "net.java.sip.communicator.impl.neomedia.TestSoundFilename";

    /**
     * Indicates if the Video/More Settings configuration tab
     * should be disabled, i.e. not visible to the user.
     */
    private static final String VIDEO_MORE_SETTINGS_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.videomoresettingsconfig.DISABLED";

    /**
     * The preferred width of all panels.
     */
    private final static int WIDTH = 350;

    /**
     * Creates the video advanced settings.
     *
     * @return video advanced settings panel.
     */
    private static Component createVideoAdvancedSettings()
    {
        ResourceManagementService resources = NeomediaActivator.getResources();

        final DeviceConfiguration deviceConfig =
            mediaService.getDeviceConfiguration();

        TransparentPanel centerPanel =
            new TransparentPanel(new GridBagLayout());
        centerPanel.setMaximumSize(new Dimension(WIDTH, 150));

        JButton resetDefaultsButton = new JButton(
            resources.getI18NString(
                    "impl.media.configform.VIDEO_RESET"));
        JPanel resetButtonPanel = new TransparentPanel(
                new FlowLayout(FlowLayout.RIGHT));
        resetButtonPanel.add(resetDefaultsButton);

        final JPanel centerAdvancedPanel
            = new TransparentPanel(new BorderLayout());
        centerAdvancedPanel.add(centerPanel, BorderLayout.NORTH);
        centerAdvancedPanel.add(resetButtonPanel, BorderLayout.SOUTH);

        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.NORTHWEST;
        constraints.insets = new Insets(5, 5, 0, 0);
        constraints.gridx = 0;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.gridy = 0;

        centerPanel.add(new JLabel(
            resources.getI18NString("impl.media.configform.VIDEO_RESOLUTION")),
            constraints);
        constraints.gridy = 1;
        constraints.insets = new Insets(0, 0, 0, 0);
        final JCheckBox frameRateCheck = new SIPCommCheckBox(
            resources.getI18NString("impl.media.configform.VIDEO_FRAME_RATE"));
        centerPanel.add(frameRateCheck, constraints);
        constraints.gridy = 2;
        constraints.insets = new Insets(5, 5, 0, 0);
        centerPanel.add(new JLabel(
            resources.getI18NString(
                    "impl.media.configform.VIDEO_PACKETS_POLICY")),
            constraints);

        constraints.weightx = 1;
        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.insets = new Insets(5, 0, 0, 5);
        Object[] resolutionValues
            = new Object[DeviceConfiguration.SUPPORTED_RESOLUTIONS.length + 1];
        System.arraycopy(DeviceConfiguration.SUPPORTED_RESOLUTIONS, 0,
                        resolutionValues, 1,
                        DeviceConfiguration.SUPPORTED_RESOLUTIONS.length);
        final JComboBox sizeCombo = new JComboBox(resolutionValues);
        sizeCombo.setRenderer(new ResolutionCellRenderer());
        sizeCombo.setEditable(false);
        centerPanel.add(sizeCombo, constraints);

        // default value is 20
        final JSpinner frameRate = new JSpinner(new SpinnerNumberModel(
            20, 5, 30, 1));
        frameRate.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                deviceConfig.setFrameRate(
                        ((SpinnerNumberModel)frameRate.getModel())
                            .getNumber().intValue());
            }
        });
        constraints.gridy = 1;
        constraints.insets = new Insets(0, 0, 0, 5);
        centerPanel.add(frameRate, constraints);

        frameRateCheck.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(frameRateCheck.isSelected())
                {
                    deviceConfig.setFrameRate(
                        ((SpinnerNumberModel)frameRate.getModel())
                            .getNumber().intValue());
                }
                else // unlimited framerate
                    deviceConfig.setFrameRate(-1);

                frameRate.setEnabled(frameRateCheck.isSelected());
            }
        });

        final JSpinner videoMaxBandwidth = new JSpinner(new SpinnerNumberModel(
            deviceConfig.getVideoMaxBandwidth(),
            1, Integer.MAX_VALUE, 1));
        videoMaxBandwidth.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                deviceConfig.setVideoMaxBandwidth(
                        ((SpinnerNumberModel)videoMaxBandwidth.getModel())
                            .getNumber().intValue());
            }
        });
        constraints.gridx = 1;
        constraints.gridy = 2;
        constraints.insets = new Insets(0, 0, 5, 5);
        centerPanel.add(videoMaxBandwidth, constraints);

        resetDefaultsButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                // reset to defaults
                sizeCombo.setSelectedIndex(0);
                frameRateCheck.setSelected(false);
                frameRate.setEnabled(false);
                frameRate.setValue(20);
                // unlimited framerate
                deviceConfig.setFrameRate(-1);
                videoMaxBandwidth.setValue(
                        DeviceConfiguration.DEFAULT_VIDEO_MAX_BANDWIDTH);
            }
        });

        // load selected value or auto
        Dimension videoSize = deviceConfig.getVideoSize();

        if((videoSize.getHeight() != DeviceConfiguration.DEFAULT_VIDEO_HEIGHT)
                && (videoSize.getWidth()
                        != DeviceConfiguration.DEFAULT_VIDEO_WIDTH))
            sizeCombo.setSelectedItem(deviceConfig.getVideoSize());
        else
            sizeCombo.setSelectedIndex(0);
        sizeCombo.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                Dimension selectedVideoSize
                    = (Dimension) sizeCombo.getSelectedItem();

                if(selectedVideoSize == null)
                {
                    // the auto value, default one
                    selectedVideoSize
                        = new Dimension(
                                DeviceConfiguration.DEFAULT_VIDEO_WIDTH,
                                DeviceConfiguration.DEFAULT_VIDEO_HEIGHT);
                }
                deviceConfig.setVideoSize(selectedVideoSize);
            }
        });

        frameRateCheck.setSelected(
            deviceConfig.getFrameRate()
                != DeviceConfiguration.DEFAULT_VIDEO_FRAMERATE);
        frameRate.setEnabled(frameRateCheck.isSelected());

        if(frameRate.isEnabled())
            frameRate.setValue(deviceConfig.getFrameRate());

        return centerAdvancedPanel;
    }

    /**
     * Creates the video container.
     * @param noVideoComponent the container component.
     * @return the video container.
     */
    private static JComponent createVideoContainer(Component noVideoComponent)
    {
        return new VideoContainer(noVideoComponent, false);
    }

    /**
     * Creates preview for the (video) device in the video container.
     *
     * @param device the device
     * @param videoContainer the video container
     * @throws IOException a problem accessing the device
     * @throws MediaException a problem getting preview
     */
    private static void createVideoPreview(
            CaptureDeviceInfo device,
            JComponent videoContainer)
        throws IOException,
               MediaException
    {
        videoContainer.removeAll();

        videoContainer.revalidate();
        videoContainer.repaint();

        if (device == null)
            return;

        for (MediaDevice mediaDevice
                : mediaService.getDevices(MediaType.VIDEO, MediaUseCase.ANY))
        {
            if(((MediaDeviceImpl) mediaDevice).getCaptureDeviceInfo().equals(
                    device))
            {
                Dimension videoContainerSize
                    = videoContainer.getPreferredSize();
                Component preview
                    = (Component)
                        mediaService.getVideoPreviewComponent(
                                mediaDevice,
                                videoContainerSize.width,
                                videoContainerSize.height);

                if (preview != null)
                    videoContainer.add(preview);
                break;
            }
        }
    }

    /**
     * The mnemonic for a type.
     * @param type audio or video type.
     * @return the mnemonic.
     */
    private static char getDisplayedMnemonic(int type)
    {
        switch (type)
        {
        case DeviceConfigurationComboBoxModel.AUDIO:
            return NeomediaActivator.getResources().getI18nMnemonic(
                "impl.media.configform.AUDIO");
        case DeviceConfigurationComboBoxModel.VIDEO:
            return NeomediaActivator.getResources().getI18nMnemonic(
                "impl.media.configform.VIDEO");
        default:
            throw new IllegalArgumentException("type");
        }
    }

    /**
     * A label for a type.
     * @param type the type.
     * @return the label.
     */
    private static String getLabelText(int type)
    {
        switch (type)
        {
        case DeviceConfigurationComboBoxModel.AUDIO:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO");
        case DeviceConfigurationComboBoxModel.AUDIO_CAPTURE:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO_IN");
        case DeviceConfigurationComboBoxModel.AUDIO_NOTIFY:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO_NOTIFY");
        case DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO_OUT");
        case DeviceConfigurationComboBoxModel.VIDEO:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.VIDEO");
        default:
            throw new IllegalArgumentException("type");
        }
    }

    /**
     * Used to move encoding options.
     *
     * @param table the table with encodings
     * @param up move direction.
     */
    private static void move(JTable table, boolean up)
    {
        int index
            = ((EncodingConfigurationTableModel) table.getModel()).move(
                    table.getSelectedRow(),
                    up);

        table.getSelectionModel().setSelectionInterval(index, index);
    }

    /**
     * The thread which updates the capture device as selected by the user. This
     * prevent the UI to lock while changing the device.
     */
    private AudioLevelListenerThread audioLevelListenerThread = null;

    /**
     * The button used to play a sound in order to test notification devices.
     */
    private JButton notificationPlaySoundButton;

    /**
     * The combo box used to selected the notification device.
     */
    private JComboBox notifyCombo;

    /**
     * The combo box used to selected the playback device.
     */
    private JComboBox playbackCombo;

    /**
     * The button used to play a sound in order to test playback device.
     */
    private JButton playbackPlaySoundButton;

    /**
     * Indicates that one of the contained in this panel buttons has been
     * clicked.
     * @param e the <tt>ActionEvent</tt> that notified us
     */
    public void actionPerformed(ActionEvent e)
    {
        boolean isPlaybackEvent = (e.getSource() == playbackPlaySoundButton);

        // If the user clicked on one pley sound button.
        if(isPlaybackEvent
                || e.getSource() == notificationPlaySoundButton)
        {
            AudioNotifierService audioNotifServ
                = NeomediaActivator.getAudioNotifierService();
            String testSoundFilename
                = NeomediaActivator.getConfigurationService()
                    .getString(
                            TEST_SOUND_FILENAME_PROP,
                            NeomediaActivator.getResources().getSoundPath(
                                "TEST_SOUND")
                            );
            SCAudioClip sound = audioNotifServ.createAudio(
                    testSoundFilename,
                    isPlaybackEvent);
            sound.play();
        }
        // If the selected item of the playback or notify combobox has changed.
        else if(e.getSource() == playbackCombo
                || e.getSource() == notifyCombo)
        {
            DeviceConfigurationComboBoxModel.CaptureDevice device
                = (DeviceConfigurationComboBoxModel.CaptureDevice)
                    ((JComboBox) e.getSource()).getSelectedItem();

            boolean isEnabled = (device.info != null);
            if(e.getSource() == playbackCombo)
            {
                playbackPlaySoundButton.setEnabled(isEnabled);
            }
            else
            {
                notificationPlaySoundButton.setEnabled(isEnabled);
            }
        }
    }

    /**
     * Returns the audio configuration panel.
     *
     * @return the audio configuration panel
     */
    public Component createAudioConfigPanel()
    {
        return createControls(DeviceConfigurationComboBoxModel.AUDIO);
    }

    /**
     * Creates the UI controls which are to control the details of a specific
     * <tt>AudioSystem</tt>.
     *
     * @param audioSystem the <tt>AudioSystem</tt> for which the UI controls to
     * control its details are to be created
     * @param container the <tt>JComponent</tt> into which the UI controls which
     * are to control the details of the specified <tt>audioSystem</tt> are to
     * be added
     */
    public void createAudioSystemControls(
            AudioSystem audioSystem,
            JComponent container)
    {
        GridBagConstraints constraints = new GridBagConstraints();

        constraints.anchor = GridBagConstraints.NORTHWEST;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.weighty = 0;

        int audioSystemFeatures = audioSystem.getFeatures();
        boolean featureNotifyAndPlaybackDevices
            = ((audioSystemFeatures
                    & AudioSystem.FEATURE_NOTIFY_AND_PLAYBACK_DEVICES)
                != 0);

        constraints.gridx = 0;
        constraints.insets = new Insets(3, 0, 3, 3);
        constraints.weightx = 0;

        constraints.gridy = 0;
        container.add(new JLabel(getLabelText(
            DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)), constraints);
        if (featureNotifyAndPlaybackDevices)
        {
            constraints.gridy = 2;
            container.add(new JLabel(getLabelText(
                DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)), constraints);
            constraints.gridy = 3;
            container.add(new JLabel(getLabelText(
                DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)), constraints);
        }

        constraints.gridx = 1;
        constraints.insets = new Insets(3, 3, 3, 0);
        constraints.weightx = 1;

        JComboBox captureCombo = null;

        if (featureNotifyAndPlaybackDevices)
        {
            captureCombo = new JComboBox();
            captureCombo.setEditable(false);
            captureCombo.setModel(
                    new DeviceConfigurationComboBoxModel(
                            captureCombo,
                            mediaService.getDeviceConfiguration(),
                            DeviceConfigurationComboBoxModel.AUDIO_CAPTURE));
            constraints.gridy = 0;
            container.add(captureCombo, constraints);
        }

        int anchor = constraints.anchor;
        SoundLevelIndicator capturePreview
            = new SoundLevelIndicator(
                    SimpleAudioLevelListener.MIN_LEVEL,
                    SimpleAudioLevelListener.MAX_LEVEL);

        constraints.anchor = GridBagConstraints.CENTER;
        constraints.gridy = (captureCombo == null) ? 0 : 1;
        container.add(capturePreview, constraints);
        constraints.anchor = anchor;

        constraints.gridy = GridBagConstraints.RELATIVE;

        if (featureNotifyAndPlaybackDevices)
        {
            playbackCombo = new JComboBox();
            playbackCombo.setEditable(false);
            playbackCombo.setModel(
                    new DeviceConfigurationComboBoxModel(
                            captureCombo,
                            mediaService.getDeviceConfiguration(),
                            DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK));
            playbackCombo.addActionListener(this);
            container.add(playbackCombo, constraints);

            notifyCombo = new JComboBox();
            notifyCombo.setEditable(false);
            notifyCombo.setModel(
                    new DeviceConfigurationComboBoxModel(
                            captureCombo,
                            mediaService.getDeviceConfiguration(),
                            DeviceConfigurationComboBoxModel.AUDIO_NOTIFY));
            notifyCombo.addActionListener(this);
            container.add(notifyCombo, constraints);
        }

        if ((AudioSystem.FEATURE_ECHO_CANCELLATION & audioSystemFeatures) != 0)
        {
            final SIPCommCheckBox echoCancelCheckBox
                = new SIPCommCheckBox(
                        NeomediaActivator.getResources().getI18NString(
                                "impl.media.configform.ECHOCANCEL"));

            /*
             * First set the selected one, then add the listener in order to
             * avoid saving the value when using the default one and only
             * showing to user without modification.
             */
            echoCancelCheckBox.setSelected(
                    mediaService.getDeviceConfiguration().isEchoCancel());
            echoCancelCheckBox.addItemListener(
                    new ItemListener()
                    {
                        public void itemStateChanged(ItemEvent e)
                        {
                            mediaService.getDeviceConfiguration().setEchoCancel(
                                    echoCancelCheckBox.isSelected());
                        }
                    });
            container.add(echoCancelCheckBox, constraints);
        }

        if ((AudioSystem.FEATURE_DENOISE & audioSystemFeatures) != 0)
        {
            final SIPCommCheckBox denoiseCheckBox
                = new SIPCommCheckBox(
                        NeomediaActivator.getResources().getI18NString(
                                "impl.media.configform.DENOISE"));

            /*
             * First set the selected one, then add the listener in order to
             * avoid saving the value when using the default one and only
             * showing to user without modification.
             */
            denoiseCheckBox.setSelected(
                    mediaService.getDeviceConfiguration().isDenoise());
            denoiseCheckBox.addItemListener(
                    new ItemListener()
                    {
                        public void itemStateChanged(ItemEvent e)
                        {
                            mediaService.getDeviceConfiguration().setDenoise(
                                    denoiseCheckBox.isSelected());
                        }
                    });
            container.add(denoiseCheckBox, constraints);
        }

        // Adds the play buttons for testing playback and notification devices.
        constraints.gridx = 2;
        constraints.insets = new Insets(3, 3, 3, 0);
        constraints.weightx = 0;

        if (featureNotifyAndPlaybackDevices)
        {
            // Playback play sound button.
            constraints.gridy = 2;
            playbackPlaySoundButton
                = new JButton(new ImageIcon(NeomediaActivator.getResources()
                            .getImageInBytes(
                                "plugin.notificationconfig.PLAY_ICON")));
            playbackPlaySoundButton.setMinimumSize(new Dimension(30,30));
            playbackPlaySoundButton.setPreferredSize(new Dimension(30,30));
            if(((DeviceConfigurationComboBoxModel.CaptureDevice)
                        playbackCombo.getSelectedItem()).info == null)
            {
                playbackPlaySoundButton.setEnabled(false);
            }
            playbackPlaySoundButton.setOpaque(false);
            playbackPlaySoundButton.addActionListener(this);
            container.add(playbackPlaySoundButton, constraints);

            // Notification play sound button.
            constraints.gridy = 3;
            notificationPlaySoundButton
                = new JButton(new ImageIcon(NeomediaActivator.getResources()
                            .getImageInBytes(
                                "plugin.notificationconfig.PLAY_ICON")));
            notificationPlaySoundButton.setMinimumSize(new Dimension(30,30));
            notificationPlaySoundButton.setPreferredSize(new Dimension(30,30));
            if(((DeviceConfigurationComboBoxModel.CaptureDevice)
                        notifyCombo.getSelectedItem()).info == null)
            {
                notificationPlaySoundButton.setEnabled(false);
            }
            notificationPlaySoundButton.setOpaque(false);
            notificationPlaySoundButton.addActionListener(this);
            container.add(notificationPlaySoundButton, constraints);
        }

        if(audioLevelListenerThread == null)
        {
            audioLevelListenerThread = new AudioLevelListenerThread(
                    audioSystem,
                    captureCombo,
                    capturePreview);
        }
        else
        {
            audioLevelListenerThread.init(
                    audioSystem,
                    captureCombo,
                    capturePreview);
        }
    }

    /**
     * Creates basic controls for a type (AUDIO or VIDEO).
     *
     * @param type the type.
     * @return the build Component.
     */
    private Component createBasicControls(final int type)
    {
        final JComboBox deviceComboBox = new JComboBox();

        deviceComboBox.setEditable(false);
        deviceComboBox.setModel(
                new DeviceConfigurationComboBoxModel(
                        deviceComboBox,
                        mediaService.getDeviceConfiguration(),
                        type));

        JLabel deviceLabel = new JLabel(getLabelText(type));

        deviceLabel.setDisplayedMnemonic(getDisplayedMnemonic(type));
        deviceLabel.setLabelFor(deviceComboBox);

        final Container devicePanel
            = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));

        devicePanel.setMaximumSize(new Dimension(WIDTH, 25));

        final boolean isAudioSystemComboDisabled
            = (type == DeviceConfigurationComboBoxModel.AUDIO)
                && NeomediaActivator.getConfigurationService().getBoolean(
                        AUDIO_SYSTEM_DISABLED_PROP,
                        false);

        // For audio configuration form first check if the audio system
        // property is disabled.
        if (!isAudioSystemComboDisabled)
        {
            devicePanel.add(deviceLabel);
            devicePanel.add(deviceComboBox);
        }

        final JPanel deviceAndPreviewPanel
            = new TransparentPanel(new BorderLayout());
        int preferredDeviceAndPreviewPanelHeight;

        switch (type)
        {
        case DeviceConfigurationComboBoxModel.AUDIO:
            preferredDeviceAndPreviewPanelHeight
                = isAudioSystemComboDisabled ? 180 : 225;
            break;
        case DeviceConfigurationComboBoxModel.VIDEO:
            preferredDeviceAndPreviewPanelHeight = 305;
            break;
        default:
            preferredDeviceAndPreviewPanelHeight = 0;
            break;
        }
        if (preferredDeviceAndPreviewPanelHeight > 0)
        {
            deviceAndPreviewPanel.setPreferredSize(
                    new Dimension(WIDTH, preferredDeviceAndPreviewPanelHeight));
        }
        deviceAndPreviewPanel.add(devicePanel, BorderLayout.NORTH);

        // For audio configuration if the audio system combo is disabled we're
        // going to look directly in the device configuration and show the
        // preview panel, which in this case contains audio configuration
        // components.
        if (isAudioSystemComboDisabled)
        {
            Component preview = null;
            if (mediaService.getDeviceConfiguration().getAudioSystem() != null)
            {
                preview = createPreview(type, deviceComboBox,
                                deviceAndPreviewPanel.getPreferredSize());
            }

            if (preview != null)
            {
                deviceAndPreviewPanel.add(preview, BorderLayout.CENTER);
            }
        }

        final ActionListener deviceComboBoxActionListener
            = new ActionListener()
            {
                public void actionPerformed(ActionEvent event)
                {
                    boolean revalidateAndRepaint = false;

                    for (int i = deviceAndPreviewPanel.getComponentCount() - 1;
                            i >= 0;
                            i--)
                    {
                        Component c = deviceAndPreviewPanel.getComponent(i);

                        if (c != devicePanel)
                        {
                            deviceAndPreviewPanel.remove(i);
                            revalidateAndRepaint = true;
                        }
                    }

                    Component preview = null;

                    if ((deviceComboBox.getSelectedItem() != null)
                            && (deviceComboBox.isShowing()
                                || isAudioSystemComboDisabled))
                    {
                        preview
                            = createPreview(
                                    type,
                                    deviceComboBox,
                                    deviceAndPreviewPanel.getPreferredSize());
                    }

                    if (preview != null)
                    {
                        deviceAndPreviewPanel.add(preview, BorderLayout.CENTER);
                        revalidateAndRepaint = true;
                    }

                    if (revalidateAndRepaint)
                    {
                        deviceAndPreviewPanel.revalidate();
                        deviceAndPreviewPanel.repaint();
                    }
                }
            };

        deviceComboBox.addActionListener(deviceComboBoxActionListener);

        /*
         * We have to initialize the controls to reflect the configuration
         * at the time of creating this instance. Additionally, because the
         * video preview will stop when it and its associated controls
         * become unnecessary, we have to restart it when the mentioned
         * controls become necessary again. We'll address the two goals
         * described by pretending there's a selection in the video combo
         * box when the combo box in question becomes displayable.
         */
        deviceComboBox.addHierarchyListener(
                new HierarchyListener()
                {
                    public void hierarchyChanged(HierarchyEvent event)
                    {
                        if ((event.getChangeFlags()
                                    & HierarchyEvent.SHOWING_CHANGED)
                                != 0)
                        {
                            SwingUtilities.invokeLater(
                                    new Runnable()
                                    {
                                        public void run()
                                        {
                                            deviceComboBoxActionListener
                                                .actionPerformed(null);
                                        }
                                    });
                        }
                    }
                });

        return deviceAndPreviewPanel;
    }

    /**
     * Creates all the controls (including encoding) for a type(AUDIO or VIDEO)
     *
     * @param type the type.
     * @return the build Component.
     */
    private Component createControls(int type)
    {
        ConfigurationService cfg = NeomediaActivator.getConfigurationService();

        Component devicesComponent = null;
        Component encodingsComponent = null;
        Component videoComponent = null;

        int compCount = 0;

        if (cfg == null || !cfg.getBoolean(DEVICES_DISABLED_PROP, false))
        {
            compCount++;
            devicesComponent = createBasicControls(type);
        }

        if (cfg == null || !cfg.getBoolean(ENCODINGS_DISABLED_PROP, false))
        {
            compCount++;
            encodingsComponent = createEncodingControls(type, null);
        }

        if (type == DeviceConfigurationComboBoxModel.VIDEO
            && (cfg == null
                || !cfg.getBoolean(VIDEO_MORE_SETTINGS_DISABLED_PROP, false)))
        {
            compCount++;
            videoComponent = createVideoAdvancedSettings();
        }

        ResourceManagementService res = NeomediaActivator.getResources();
        Container container;

        // If we only have one configuration form we don't need to create
        // a tabbed pane.
        if (compCount < 2)
        {
            container = new TransparentPanel(new BorderLayout());

            if (devicesComponent != null)
                container.add(devicesComponent);
            else if (encodingsComponent != null)
                container.add(encodingsComponent);
            else if (videoComponent != null)
                container.add(videoComponent);
        }
        else
        {
            container = new SIPCommTabbedPane();

            SIPCommTabbedPane tabbedPane = (SIPCommTabbedPane) container;

            int index = 0;

            if (devicesComponent != null)
            {
                tabbedPane.insertTab(
                    res.getI18NString("impl.media.configform.DEVICES"),
                    null,
                    devicesComponent,
                    null,
                    index);

                index = 1;
            }

            if (encodingsComponent != null)
            {
                if (tabbedPane.getTabCount() >= 1)
                    index = 1;

                tabbedPane.insertTab(
                    res.getI18NString("impl.media.configform.ENCODINGS"),
                    null,
                    encodingsComponent,
                    null,
                    index);
            }

            if (videoComponent != null)
            {
                if (tabbedPane.getTabCount() >= 2)
                    index = 2;

                tabbedPane.insertTab(
                    res.getI18NString(
                        "impl.media.configform.VIDEO_MORE_SETTINGS"),
                    null,
                    videoComponent,
                    null,
                    index);
            }
        }

        return container;
    }

    /**
     * Creates Component for the encodings of type(AUDIO or VIDEO).
     *
     * @param type the type, either DeviceConfigurationComboBoxModel.AUDIO or
     * DeviceConfigurationComboBoxModel.AUDIO
     * @param encodingConfiguration The <tt>EncodingConfiguration</tt> instance
     * to use. If null, it will use the current encoding configuration from
     * the media service.
     * @return the component.
     */
    private Component createEncodingControls(int type,
            EncodingConfiguration encodingConfiguration)
    {
        if(encodingConfiguration == null)
        {
            encodingConfiguration
                    = mediaService.getCurrentEncodingConfiguration();
        }

        ResourceManagementService resources = NeomediaActivator.getResources();
        String key;

        final JTable table = new JTable();
        table.setShowGrid(false);
        table.setTableHeader(null);
        table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
        {
            @Override
            public Component getTableCellRendererComponent(JTable rtable,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column)
            {
                Component component = super.getTableCellRendererComponent(
                    rtable, value, isSelected, hasFocus, row, column);
                component.setEnabled(rtable != null && rtable.isEnabled());
                return component;
            }
        });

        key = "impl.media.configform.UP";
        final JButton upButton = new JButton(resources.getI18NString(key));
        upButton.setMnemonic(resources.getI18nMnemonic(key));
        upButton.setOpaque(false);

        key = "impl.media.configform.DOWN";
        final JButton downButton = new JButton(resources.getI18NString(key));
        downButton.setMnemonic(resources.getI18nMnemonic(key));
        downButton.setOpaque(false);

        Container buttonBar = new TransparentPanel(new GridLayout(0, 1));
        buttonBar.add(upButton);
        buttonBar.add(downButton);

        Container parentButtonBar = new TransparentPanel(new BorderLayout());
        parentButtonBar.add(buttonBar, BorderLayout.NORTH);

        table.setModel(new EncodingConfigurationTableModel(type,
                encodingConfiguration));
        /*
         * The first column contains the check boxes which enable/disable their
         * associated encodings and it doesn't make sense to make it wider than
         * the check boxes.
         */
        TableColumnModel tableColumnModel = table.getColumnModel();
        TableColumn tableColumn = tableColumnModel.getColumn(0);
        tableColumn.setMaxWidth(tableColumn.getMinWidth());

        final ListSelectionListener tableSelectionListener =
            new ListSelectionListener()
            {
                public void valueChanged(ListSelectionEvent event)
                {
                    if (table.getSelectedRowCount() == 1)
                    {
                        int selectedRow = table.getSelectedRow();
                        if (selectedRow > -1)
                        {
                            upButton.setEnabled(selectedRow > 0);
                            downButton.setEnabled(selectedRow < (table
                                .getRowCount() - 1));
                            return;
                        }
                    }
                    upButton.setEnabled(false);
                    downButton.setEnabled(false);
                }
            };
        table.getSelectionModel().addListSelectionListener(
            tableSelectionListener);
        tableSelectionListener.valueChanged(null);

        ActionListener buttonListener = new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                Object source = event.getSource();
                boolean up;
                if (source == upButton)
                    up = true;
                else if (source == downButton)
                    up = false;
                else
                    return;

                move(table, up);
            }
        };
        upButton.addActionListener(buttonListener);
        downButton.addActionListener(buttonListener);

        Container container = new TransparentPanel(new BorderLayout())
        {
            @Override
            public void setEnabled(boolean enabled)
            {
                super.setEnabled(enabled);
                table.setEnabled(enabled);
                if (enabled)
                {
                    tableSelectionListener.valueChanged(null);
                }
                else
                {
                    upButton.setEnabled(false);
                    downButton.setEnabled(false);
                }
            }
        };
        container.setPreferredSize(new Dimension(WIDTH, 100));
        container.setMaximumSize(new Dimension(WIDTH, 100));

        container.add(new JScrollPane(table), BorderLayout.CENTER);
        container.add(parentButtonBar, BorderLayout.EAST);
        return container;
    }

    /**
     * Returns a component for encodings configuration for the given
     * <tt>mediaType</tt>
     *
     * @param mediaType Either <tt>MediaType.AUDIO</tt> or
     * <tt>MediaType.VIDEO</tt>
     * @param encodingConfiguration The <tt>EncodingConfiguration</tt> instance
     * to use. If null, it will use the current encoding configuration from
     * the media service.
     * @return The component for encodings configuration.
     */
    public Component createEncodingControls(
            MediaType mediaType,
            EncodingConfiguration encodingConfiguration)
    {
        if(encodingConfiguration == null)
        {
            encodingConfiguration
                = mediaService.getCurrentEncodingConfiguration();
        }

        int deviceConfigurationComboBoxModelType;

        switch (mediaType)
        {
        case AUDIO:
            deviceConfigurationComboBoxModelType
                = DeviceConfigurationComboBoxModel.AUDIO;
            break;
        case VIDEO:
            deviceConfigurationComboBoxModelType
                = DeviceConfigurationComboBoxModel.VIDEO;
            break;
        default:
            throw new IllegalArgumentException("mediaType");
        }

        return
            createEncodingControls(
                    deviceConfigurationComboBoxModelType,
                    encodingConfiguration);
    }

    /**
     * Create preview component.
     * @param type type
     * @param comboBox the options.
     * @param prefSize the preferred size
     * @return the component.
     */
    private Component createPreview(
            int type,
            final JComboBox comboBox,
            Dimension prefSize)
    {
        JComponent preview = null;

        if (type == DeviceConfigurationComboBoxModel.AUDIO)
        {
            AudioSystem audioSystem = null;

            // If the Audio System combo box is disabled we're looking for the
            // default device configuration.
            if (NeomediaActivator.getConfigurationService()
                    .getBoolean(AUDIO_SYSTEM_DISABLED_PROP, false)
                && mediaService.getDeviceConfiguration().getAudioSystem()
                    != null)
            {
                audioSystem
                    = mediaService.getDeviceConfiguration().getAudioSystem();
            }
            else
            {
                Object selectedItem = comboBox.getSelectedItem();

                if (selectedItem instanceof AudioSystem)
                    audioSystem = (AudioSystem) selectedItem;
            }

            if (audioSystem != null
                && !NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(
                    audioSystem.getLocatorProtocol()))
            {
                preview = new TransparentPanel(new GridBagLayout());
                createAudioSystemControls(audioSystem, preview);
            }
            else
            {
                AudioSystem[] availableAudioSystems
                    = AudioSystem.getAudioSystems();
                AudioSystem[] activeAudioSystems = mediaService
                    .getDeviceConfiguration().getAvailableAudioSystems();

                // If the only one active audio system which is "None" and there
                // is(are) other(s) available audio system(s), then it means
                // that the other(s) audio system(s) do(es) not have detected
                // any device.
                if(availableAudioSystems != null
                        && availableAudioSystems.length > 1
                        && activeAudioSystems != null
                        && activeAudioSystems.length == 1)
                {
                    String noAvailableAudioDevice
                        = NeomediaActivator.getResources().getI18NString(
                            "impl.media.configform.NO_AVAILABLE_AUDIO_DEVICE");
                    preview = new TransparentPanel(new GridBagLayout());
                    preview.add(new JLabel(noAvailableAudioDevice));
                }
            }
        }
        else if (type == DeviceConfigurationComboBoxModel.VIDEO)
        {
            JLabel noPreview
                = new JLabel(
                        NeomediaActivator.getResources().getI18NString(
                                "impl.media.configform.NO_PREVIEW"));

            noPreview.setHorizontalAlignment(SwingConstants.CENTER);
            noPreview.setVerticalAlignment(SwingConstants.CENTER);

            preview = createVideoContainer(noPreview);
            preview.setPreferredSize(prefSize);

            Object selectedItem = comboBox.getSelectedItem();
            CaptureDeviceInfo device = null;
            if (selectedItem
                    instanceof
                        DeviceConfigurationComboBoxModel.CaptureDevice)
                device
                    = ((DeviceConfigurationComboBoxModel.CaptureDevice)
                            selectedItem)
                        .info;

            Exception exception;
            try
            {
                createVideoPreview(device, preview);
                exception = null;
            }
            catch (IOException ex)
            {
                exception = ex;
            }
            catch (MediaException ex)
            {
                exception = ex;
            }
            if (exception != null)
            {
                logger.error(
                    "Failed to create preview for device " + device,
                    exception);
                device = null;
            }
        }

        return preview;
    }

    /**
     * Returns the video configuration panel.
     *
     * @return the video configuration panel
     */
    public Component createVideoConfigPanel()
    {
        return createControls(DeviceConfigurationComboBoxModel.VIDEO);
    }

    /**
     * Returns the <tt>MediaService</tt> instance.
     *
     * @return the <tt>MediaService</tt> instance
     */
    public MediaService getMediaService()
    {
        return mediaService;
    }
}
TOP

Related Classes of net.java.sip.communicator.impl.neomedia.MediaConfigurationImpl

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.