mirror of https://github.com/sipwise/jitsi.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
632 lines
23 KiB
632 lines
23 KiB
/*
|
|
* SIP Communicator, 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 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.impl.neomedia.device.*;
|
|
import net.java.sip.communicator.service.neomedia.*;
|
|
import net.java.sip.communicator.service.resources.*;
|
|
import net.java.sip.communicator.util.*;
|
|
import net.java.sip.communicator.util.swing.*;
|
|
|
|
import org.osgi.framework.*;
|
|
|
|
/**
|
|
* @author Lubomir Marinov
|
|
* @author Damian Minkov
|
|
*/
|
|
public class MediaConfigurationPanel
|
|
extends TransparentPanel
|
|
{
|
|
private static final int HGAP = 5;
|
|
|
|
private static final int VGAP = 5;
|
|
|
|
private static MediaServiceImpl getMediaService()
|
|
{
|
|
BundleContext bundleContext = NeomediaActivator.getBundleContext();
|
|
ServiceReference serviceReference
|
|
= bundleContext.getServiceReference(MediaService.class.getName());
|
|
|
|
return
|
|
(serviceReference == null)
|
|
? null
|
|
: (MediaServiceImpl) bundleContext.getService(serviceReference);
|
|
}
|
|
|
|
private final Logger logger
|
|
= Logger.getLogger(MediaConfigurationPanel.class);
|
|
|
|
private final MediaServiceImpl mediaService = getMediaService();
|
|
|
|
/**
|
|
* The video <code>CaptureDeviceInfo</code> this instance started to create
|
|
* the preview of.
|
|
* <p>
|
|
* Because the creation of the preview is asynchronous, it's possible to
|
|
* request the preview of one and the same device multiple times. Which may
|
|
* lead to failures because of, for example, busy devices and/or resources
|
|
* (as is the case with LTI-CIVIL and video4linux2).
|
|
* </p>
|
|
*/
|
|
private CaptureDeviceInfo videoDeviceInPreview;
|
|
|
|
/**
|
|
* The <code>Player</code> depicting the preview of the currently selected
|
|
* <code>CaptureDeviceInfo</code>.
|
|
*/
|
|
private Player videoPlayerInPreview;
|
|
|
|
/**
|
|
* Creates the panel.
|
|
*/
|
|
public MediaConfigurationPanel()
|
|
{
|
|
super(new GridLayout(0, 1, HGAP, VGAP));
|
|
|
|
int[] types
|
|
= new int[]
|
|
{
|
|
DeviceConfigurationComboBoxModel.AUDIO,
|
|
DeviceConfigurationComboBoxModel.VIDEO
|
|
};
|
|
|
|
for (int type : types)
|
|
add(createControls(type));
|
|
}
|
|
|
|
private void controllerUpdateForPreview(ControllerEvent event,
|
|
Container videoContainer)
|
|
{
|
|
if (event instanceof RealizeCompleteEvent)
|
|
{
|
|
Player player = (Player) event.getSourceController();
|
|
Component video = player.getVisualComponent();
|
|
|
|
showPreview(videoContainer, video, player);
|
|
}
|
|
}
|
|
|
|
private void createPortAudioControls(Container portAudioPanel)
|
|
{
|
|
portAudioPanel.add(new JLabel(getLabelText(
|
|
DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)));
|
|
JComboBox captureCombo = new JComboBox();
|
|
captureCombo.setEditable(false);
|
|
captureCombo.setModel(
|
|
new DeviceConfigurationComboBoxModel(
|
|
mediaService.getDeviceConfiguration(),
|
|
DeviceConfigurationComboBoxModel.AUDIO_CAPTURE));
|
|
portAudioPanel.add(captureCombo);
|
|
|
|
portAudioPanel.add(new JLabel(getLabelText(
|
|
DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)));
|
|
JComboBox playbackCombo = new JComboBox();
|
|
playbackCombo.setEditable(false);
|
|
playbackCombo.setModel(
|
|
new DeviceConfigurationComboBoxModel(
|
|
mediaService.getDeviceConfiguration(),
|
|
DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK));
|
|
portAudioPanel.add(playbackCombo);
|
|
|
|
portAudioPanel.add(new JLabel(getLabelText(
|
|
DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)));
|
|
JComboBox notifyCombo = new JComboBox();
|
|
notifyCombo.setEditable(false);
|
|
notifyCombo.setModel(
|
|
new DeviceConfigurationComboBoxModel(
|
|
mediaService.getDeviceConfiguration(),
|
|
DeviceConfigurationComboBoxModel.AUDIO_NOTIFY));
|
|
portAudioPanel.add(notifyCombo);
|
|
}
|
|
|
|
private Component createControls(int type)
|
|
{
|
|
final JComboBox comboBox = new JComboBox();
|
|
comboBox.setEditable(false);
|
|
comboBox
|
|
.setModel(
|
|
new DeviceConfigurationComboBoxModel(
|
|
mediaService.getDeviceConfiguration(),
|
|
type));
|
|
|
|
/*
|
|
* We provide additional configuration properties for PortAudio such as
|
|
* input audio device, output audio device and audio device for playback
|
|
* of notifications.
|
|
*/
|
|
final Container portAudioPanel;
|
|
if (type == DeviceConfigurationComboBoxModel.AUDIO)
|
|
{
|
|
portAudioPanel
|
|
= new TransparentPanel(new GridLayout(3, 2, HGAP, VGAP));
|
|
|
|
comboBox.addItemListener(new ItemListener()
|
|
{
|
|
public void itemStateChanged(ItemEvent e)
|
|
{
|
|
if(e.getStateChange() == ItemEvent.SELECTED)
|
|
{
|
|
if(DeviceConfiguration
|
|
.AUDIO_SYSTEM_PORTAUDIO.equals(e.getItem()))
|
|
{
|
|
createPortAudioControls(portAudioPanel);
|
|
}
|
|
else
|
|
{
|
|
portAudioPanel.removeAll();
|
|
|
|
revalidate();
|
|
repaint();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (DeviceConfiguration
|
|
.AUDIO_SYSTEM_PORTAUDIO.equals(comboBox.getSelectedItem()))
|
|
createPortAudioControls(portAudioPanel);
|
|
}
|
|
else
|
|
portAudioPanel = null;
|
|
|
|
JLabel label = new JLabel(getLabelText(type));
|
|
label.setDisplayedMnemonic(getDisplayedMnemonic(type));
|
|
label.setLabelFor(comboBox);
|
|
|
|
Container firstContainer = new TransparentPanel(new GridBagLayout());
|
|
GridBagConstraints firstConstraints = new GridBagConstraints();
|
|
firstConstraints.anchor = GridBagConstraints.NORTHWEST;
|
|
firstConstraints.gridx = 0;
|
|
firstConstraints.gridy = 0;
|
|
firstConstraints.weightx = 0;
|
|
firstContainer.add(label, firstConstraints);
|
|
firstConstraints.gridx = 1;
|
|
firstConstraints.weightx = 1;
|
|
firstContainer.add(comboBox, firstConstraints);
|
|
|
|
if (portAudioPanel != null)
|
|
{
|
|
firstConstraints.gridx = 0;
|
|
firstConstraints.gridy = 1;
|
|
firstConstraints.weightx = 1;
|
|
firstConstraints.gridwidth = 2;
|
|
firstConstraints.insets = new Insets(VGAP, 0, 0, 0);
|
|
firstContainer.add(portAudioPanel, firstConstraints);
|
|
}
|
|
|
|
Container secondContainer =
|
|
new TransparentPanel(new GridLayout(1, 0, HGAP, VGAP));
|
|
secondContainer.add(createPreview(type, comboBox));
|
|
secondContainer.add(createEncodingControls(type));
|
|
|
|
Container container = new TransparentPanel(new GridBagLayout());
|
|
GridBagConstraints constraints = new GridBagConstraints();
|
|
constraints.fill = GridBagConstraints.HORIZONTAL;
|
|
constraints.gridx = 0;
|
|
constraints.gridy = 0;
|
|
constraints.weightx = 1;
|
|
constraints.weighty = 0;
|
|
container.add(firstContainer, constraints);
|
|
constraints.fill = GridBagConstraints.BOTH;
|
|
constraints.gridy = 1;
|
|
constraints.weighty = 1;
|
|
container.add(secondContainer, constraints);
|
|
|
|
return container;
|
|
}
|
|
|
|
private Component createEncodingControls(int type)
|
|
{
|
|
ResourceManagementService resources = NeomediaActivator.getResources();
|
|
String key;
|
|
|
|
final JTable table = new JTable();
|
|
table.setShowGrid(false);
|
|
table.setTableHeader(null);
|
|
|
|
key = "impl.media.configform.ENCODINGS";
|
|
JLabel label = new JLabel(resources.getI18NString(key));
|
|
label.setDisplayedMnemonic(resources.getI18nMnemonic(key));
|
|
label.setLabelFor(table);
|
|
|
|
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 container = new TransparentPanel(new GridBagLayout());
|
|
GridBagConstraints constraints = new GridBagConstraints();
|
|
constraints.anchor = GridBagConstraints.NORTHWEST;
|
|
constraints.fill = GridBagConstraints.HORIZONTAL;
|
|
constraints.gridwidth = 2;
|
|
constraints.gridx = 0;
|
|
constraints.gridy = 0;
|
|
constraints.weightx = 0;
|
|
constraints.weighty = 0;
|
|
container.add(label, constraints);
|
|
constraints.anchor = GridBagConstraints.CENTER;
|
|
constraints.fill = GridBagConstraints.BOTH;
|
|
constraints.gridwidth = 1;
|
|
constraints.gridx = 0;
|
|
constraints.gridy = 1;
|
|
constraints.weightx = 1;
|
|
constraints.weighty = 1;
|
|
container.add(new JScrollPane(table), constraints);
|
|
constraints.anchor = GridBagConstraints.NORTHEAST;
|
|
constraints.fill = GridBagConstraints.NONE;
|
|
constraints.gridwidth = 1;
|
|
constraints.gridx = 1;
|
|
constraints.gridy = 1;
|
|
constraints.weightx = 0;
|
|
constraints.weighty = 0;
|
|
container.add(buttonBar, constraints);
|
|
|
|
table.setModel(new EncodingConfigurationTableModel(mediaService
|
|
.getEncodingConfiguration(), type));
|
|
|
|
/*
|
|
* 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());
|
|
|
|
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);
|
|
|
|
return container;
|
|
}
|
|
|
|
private void createPreview(CaptureDeviceInfo device,
|
|
final Container videoContainer)
|
|
throws IOException,
|
|
MediaException
|
|
{
|
|
videoContainer.removeAll();
|
|
if (videoPlayerInPreview != null)
|
|
disposePlayer(videoPlayerInPreview);
|
|
|
|
if (device == null)
|
|
return;
|
|
|
|
DataSource dataSource = Manager.createDataSource(device.getLocator());
|
|
|
|
Dimension size = videoContainer.getPreferredSize();
|
|
VideoMediaStreamImpl
|
|
.selectVideoSize(dataSource, size.width, size.height);
|
|
|
|
Player player = Manager.createPlayer(dataSource);
|
|
|
|
videoPlayerInPreview = player;
|
|
|
|
player.addControllerListener(new ControllerListener()
|
|
{
|
|
public void controllerUpdate(ControllerEvent event)
|
|
{
|
|
controllerUpdateForPreview(event, videoContainer);
|
|
}
|
|
});
|
|
player.start();
|
|
}
|
|
|
|
private Component createPreview(int type, final JComboBox comboBox)
|
|
{
|
|
final Container preview;
|
|
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);
|
|
|
|
final ActionListener comboBoxListener = new ActionListener()
|
|
{
|
|
public void actionPerformed(ActionEvent event)
|
|
{
|
|
Object selection = comboBox.getSelectedItem();
|
|
CaptureDeviceInfo device = null;
|
|
if (selection
|
|
instanceof
|
|
DeviceConfigurationComboBoxModel.CaptureDevice)
|
|
device
|
|
= ((DeviceConfigurationComboBoxModel.CaptureDevice)
|
|
selection)
|
|
.info;
|
|
|
|
if ((device != null) && device.equals(videoDeviceInPreview))
|
|
return;
|
|
|
|
Exception exception;
|
|
try
|
|
{
|
|
createPreview(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;
|
|
}
|
|
|
|
videoDeviceInPreview = device;
|
|
}
|
|
};
|
|
comboBox.addActionListener(comboBoxListener);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
comboBox.addHierarchyListener(new HierarchyListener()
|
|
{
|
|
public void hierarchyChanged(HierarchyEvent event)
|
|
{
|
|
if (((event.getChangeFlags()
|
|
& HierarchyEvent.DISPLAYABILITY_CHANGED)
|
|
!= 0)
|
|
&& comboBox.isDisplayable())
|
|
comboBoxListener.actionPerformed(null);
|
|
}
|
|
});
|
|
} else
|
|
preview = new TransparentPanel();
|
|
return preview;
|
|
}
|
|
|
|
private Container createVideoContainer(Component noVideoComponent)
|
|
{
|
|
return new VideoContainer(noVideoComponent);
|
|
}
|
|
|
|
private void disposePlayer(Player player)
|
|
{
|
|
player.stop();
|
|
player.deallocate();
|
|
player.close();
|
|
|
|
if ((videoPlayerInPreview != null)
|
|
&& videoPlayerInPreview.equals(player))
|
|
videoPlayerInPreview = null;
|
|
}
|
|
|
|
private 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");
|
|
}
|
|
}
|
|
|
|
private 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");
|
|
}
|
|
}
|
|
|
|
private void move(JTable table, boolean up)
|
|
{
|
|
int index =
|
|
((EncodingConfigurationTableModel) table.getModel()).move(table
|
|
.getSelectedRow(), up);
|
|
table.getSelectionModel().setSelectionInterval(index, index);
|
|
}
|
|
|
|
private void showPreview(final Container previewContainer,
|
|
final Component preview, final Player player)
|
|
{
|
|
if (!SwingUtilities.isEventDispatchThread())
|
|
{
|
|
SwingUtilities.invokeLater(new Runnable()
|
|
{
|
|
public void run()
|
|
{
|
|
showPreview(previewContainer, preview, player);
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
previewContainer.removeAll();
|
|
|
|
if (preview != null)
|
|
{
|
|
HierarchyListener hierarchyListener = new HierarchyListener()
|
|
{
|
|
private Window window;
|
|
|
|
private WindowListener windowListener;
|
|
|
|
public void dispose()
|
|
{
|
|
if (windowListener != null)
|
|
{
|
|
if (window != null)
|
|
{
|
|
window.removeWindowListener(windowListener);
|
|
window = null;
|
|
}
|
|
windowListener = null;
|
|
}
|
|
preview.removeHierarchyListener(this);
|
|
|
|
disposePlayer(player);
|
|
videoDeviceInPreview = null;
|
|
|
|
/*
|
|
* We've just disposed the player which created the preview
|
|
* component so the preview component is of no use
|
|
* regardless of whether the Media configuration form will
|
|
* be redisplayed or not. And since the preview component
|
|
* appears to be a huge object even after its player is
|
|
* disposed, make sure to not reference it.
|
|
*/
|
|
previewContainer.remove(preview);
|
|
}
|
|
|
|
public void hierarchyChanged(HierarchyEvent event)
|
|
{
|
|
if ((event.getChangeFlags()
|
|
& HierarchyEvent.DISPLAYABILITY_CHANGED)
|
|
!= 0)
|
|
{
|
|
if (preview.isDisplayable())
|
|
{
|
|
if (windowListener == null)
|
|
{
|
|
window =
|
|
SwingUtilities.windowForComponent(preview);
|
|
if (window != null)
|
|
{
|
|
windowListener = new WindowAdapter()
|
|
{
|
|
public void windowClosing(
|
|
WindowEvent event)
|
|
{
|
|
dispose();
|
|
}
|
|
};
|
|
window.addWindowListener(windowListener);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dispose();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
preview.addHierarchyListener(hierarchyListener);
|
|
|
|
previewContainer.add(preview);
|
|
}
|
|
else
|
|
disposePlayer(player);
|
|
}
|
|
}
|