diff options
Diffstat (limited to 'src')
28 files changed, 1360 insertions, 219 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java b/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java new file mode 100644 index 0000000..a597c55 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/AbstractCallToggleButton.java @@ -0,0 +1,95 @@ +/* + * 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.gui.main.call; + +import java.awt.event.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.resources.*; +import net.java.sip.communicator.util.swing.*; + +/** + * The base class for all toggle buttons which control the call from the UI. + * Allows extending buttons to focus on performing their toggle actions. + * + * @author Dmitri Melnikov + */ +public abstract class AbstractCallToggleButton + extends SIPCommToggleButton + implements ActionListener +{ + /** + * The <tt>Call</tt> that this button controls. + */ + protected final Call call; + + /** + * Initializes a new <tt>AbstractCallToggleButton</tt> instance which is to + * control a toggle action for a specific <tt>Call</tt>. + * + * @param call the <tt>Call</tt> to be controlled by the instance + * @param fullScreen <tt>true</tt> if the new instance is to be used in + * full-screen UI; otherwise, <tt>false</tt> + * @param selected <tt>true</tt> if the new toggle button is to be initially + * selected; otherwise, <tt>false</tt> + * @param iconImageID the <tt>ImageID</tt> of the image to be used as the + * icon of the new instance + * @param toolTipTextKey the key in the <tt>ResourceManagementService</tt> + * of the internationalized string which is to be used as the tool tip text + * of the new instance + */ + public AbstractCallToggleButton( + Call call, + boolean fullScreen, + boolean selected, + ImageID iconImageID, + String toolTipTextKey) + { + this.call = call; + + ImageID bgImage; + ImageID bgRolloverImage; + ImageID pressedImage; + + if (fullScreen) + { + bgImage = ImageLoader.FULL_SCREEN_BUTTON_BG; + bgRolloverImage = ImageLoader.FULL_SCREEN_BUTTON_BG; + pressedImage = ImageLoader.FULL_SCREEN_BUTTON_BG_PRESSED; + } + else + { + bgImage = ImageLoader.CALL_SETTING_BUTTON_BG; + bgRolloverImage = ImageLoader.CALL_SETTING_BUTTON_BG; + pressedImage = ImageLoader.CALL_SETTING_BUTTON_PRESSED_BG; + } + setBgImage(ImageLoader.getImage(bgImage)); + setBgRolloverImage(ImageLoader.getImage(bgRolloverImage)); + setPressedImage(ImageLoader.getImage(pressedImage)); + + setIconImage(ImageLoader.getImage(iconImageID)); + if (toolTipTextKey != null) + { + setToolTipText( + GuiActivator.getResources().getI18NString(toolTipTextKey)); + } + + addActionListener(this); + setSelected(selected); + } + + /** + * Notifies this <tt>AbstractCallToggleButton</tt> that its associated + * action has been performed and that it should execute its very logic. + * + * @param evt an <tt>ActionEvent</tt> which describes the specifics of the + * performed action + */ + public abstract void actionPerformed(ActionEvent evt); +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java b/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java index 9a1d8e5..0da2ab6 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallDialog.java @@ -87,6 +87,12 @@ public class CallDialog private MuteButton muteButton;
/**
+ * The button which allows starting and stopping the recording of the
+ * {@link #call}.
+ */
+ private RecordButton recordButton;
+
+ /**
* The video button.
*/
private LocalVideoButton videoButton;
@@ -206,6 +212,7 @@ public class CallDialog holdButton = new HoldButton(call);
muteButton = new MuteButton(call);
+ recordButton = new RecordButton(call);
videoButton = new LocalVideoButton(call);
transferCallButton = new TransferCallButton(call);
fullScreenButton = new FullScreenButton(this);
@@ -229,17 +236,21 @@ public class CallDialog GuiActivator.getResources().getI18NString("service.gui.HANG_UP"));
hangupButton.addActionListener(this);
- // Buttons would be enabled once the call has entered in state
- // connected.
+ /*
+ * The buttons will be enabled once the call has entered in a connected
+ * state.
+ */
dialButton.setEnabled(false);
conferenceButton.setEnabled(false);
holdButton.setEnabled(false);
muteButton.setEnabled(false);
+ recordButton.setEnabled(false);
settingsPanel.add(dialButton);
settingsPanel.add(conferenceButton);
settingsPanel.add(holdButton);
settingsPanel.add(muteButton);
+ settingsPanel.add(recordButton);
if (!isLastConference)
{
@@ -454,6 +465,7 @@ public class CallDialog conferenceButton.setEnabled(true);
holdButton.setEnabled(true);
muteButton.setEnabled(true);
+ recordButton.setEnabled(true);
if (!isLastConference)
{
diff --git a/src/net/java/sip/communicator/impl/gui/main/call/HoldButton.java b/src/net/java/sip/communicator/impl/gui/main/call/HoldButton.java index 040afdd..2f96696 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/HoldButton.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/HoldButton.java @@ -9,11 +9,9 @@ package net.java.sip.communicator.impl.gui.main.call; import java.awt.event.*; import java.util.*; -import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; -import net.java.sip.communicator.util.swing.*; /** * Represents an UI means to put an associated <tt>CallPariticant</tt> on/off @@ -21,24 +19,30 @@ import net.java.sip.communicator.util.swing.*; * * @author Lubomir Marinov * @author Yana Stamcheva + * @author Dmitri Melnikov */ public class HoldButton - extends SIPCommToggleButton - implements ActionListener + extends AbstractCallToggleButton { - private static final long serialVersionUID = 0L; - + /** + * The <tt>Logger</tt> used by the <tt>HoldButton</tt> class and its + * instances for logging output. + */ private static final Logger logger = Logger.getLogger(HoldButton.class); - private final Call call; + /** + * The serialization-related version of the <tt>HoldButton</tt> class + * explicitly defined to silence a related warning (e.g. in Eclipse IDE) + * since the <tt>HoldButton</tt> class does not add instance fields. + */ + private static final long serialVersionUID = 0L; /** * Initializes a new <tt>HoldButton</tt> instance which is to put a specific - * <tt>CallPeer</tt> on/off hold. + * <tt>Call</tt> on/off hold. * - * @param call the <tt>Call</tt> to be associated with - * the new instance and to be put on/off hold upon performing its - * action + * @param call the <tt>Call</tt> to be associated with the new instance and + * to be put on/off hold upon performing its action */ public HoldButton(Call call) { @@ -49,66 +53,39 @@ public class HoldButton * Initializes a new <tt>HoldButton</tt> instance which is to put a specific * <tt>CallPeer</tt> on/off hold. * - * @param call the <tt>Call</tt> to be associated with - * the new instance and to be put on/off hold upon performing - * its action. - * @param isFullScreenMode indicates if this button will be used in a normal - * or full screen mode. - * @param isSelected indicates the initial state of this toggle button - - * selected or not. + * @param call the <tt>Call</tt> to be associated with the new instance and + * to be put on/off hold upon performing its action + * @param fullScreen <tt>true</tt> if the new instance is to be used in + * full-screen UI; otherwise, <tt>false</tt> + * @param selected <tt>true</tt> if the new toggle button is to be initially + * selected; otherwise, <tt>false</tt> */ - public HoldButton( Call call, - boolean isFullScreenMode, - boolean isSelected) + public HoldButton(Call call, boolean fullScreen, boolean selected) { - this.call = call; - - if (isFullScreenMode) - { - this.setBgImage( - ImageLoader.getImage(ImageLoader.FULL_SCREEN_BUTTON_BG)); - this.setBgRolloverImage( - ImageLoader.getImage(ImageLoader.FULL_SCREEN_BUTTON_BG)); - this.setIconImage( - ImageLoader.getImage(ImageLoader.HOLD_BUTTON)); - this.setPressedImage( - ImageLoader.getImage(ImageLoader.FULL_SCREEN_BUTTON_BG_PRESSED)); - } - else - { - this.setBgImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); - this.setBgRolloverImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); - this.setIconImage( - ImageLoader.getImage(ImageLoader.HOLD_BUTTON)); - this.setPressedImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_PRESSED_BG)); - } - - this.addActionListener(this); - setToolTipText(GuiActivator.getResources().getI18NString( - "service.gui.HOLD_BUTTON_TOOL_TIP")); - setSelected(isSelected); + super( + call, + fullScreen, + selected, + ImageLoader.HOLD_BUTTON, + "service.gui.HOLD_BUTTON_TOOL_TIP"); } /** - * Holds on or off call peers when the hold button is clicked. - * @param evt the <tt>ActionEvent</tt> that notified us of the action + * Holds on or off the associated <tt>Call</tt> when this button is clicked. + * + * @param evt an <tt>ActionEvent</tt> which describes the specifics of the + * performed action + * @see AbstractCallToggleButton#actionPerformed(ActionEvent) */ public void actionPerformed(ActionEvent evt) { if (call != null) { - OperationSetBasicTelephony telephony = - call.getProtocolProvider() - .getOperationSet(OperationSetBasicTelephony.class); - Iterator<? extends CallPeer> peers = call.getCallPeers(); - - // Obtain the isSelected property before invoking putOnHold, - // because the property could change after putting on/off hold. - boolean isHoldSelected = isSelected(); + boolean on = isSelected(); + OperationSetBasicTelephony telephony + = call.getProtocolProvider().getOperationSet( + OperationSetBasicTelephony.class); while (peers.hasNext()) { @@ -116,17 +93,16 @@ public class HoldButton try { - if (isHoldSelected) + if (on) telephony.putOnHold(callPeer); else telephony.putOffHold(callPeer); } - catch (OperationFailedException ex) + catch (OperationFailedException ofex) { - if (isHoldSelected) - logger.error("Failed to put on hold.", ex); - else - logger.error("Failed to put off hold.", ex); + logger.error( + "Failed to put " + (on ? "on" : "off") + " hold.", + ofex); } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/MuteButton.java b/src/net/java/sip/communicator/impl/gui/main/call/MuteButton.java index d6be652..e81e074 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/MuteButton.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/MuteButton.java @@ -8,32 +8,33 @@ package net.java.sip.communicator.impl.gui.main.call; import java.awt.event.*; -import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.swing.*; /** - * Represents an UI means to mute the audio stream sent to an associated - * <tt>CallPariticant</tt>. + * Represents an UI means to mute the audio stream sent in an associated + * <tt>Call</tt>. * * @author Lubomir Marinov * @author Yana Stamcheva + * @author Dmitri Melnikov */ public class MuteButton - extends SIPCommToggleButton - implements ActionListener + extends AbstractCallToggleButton { + /** + * The serialization-related version of the <tt>MuteButton</tt> class + * explicitly defined to silence a related warning (e.g. in Eclipse IDE) + * since the <tt>MuteButton</tt> class does not add instance fields. + */ private static final long serialVersionUID = 0L; - private final Call call; - /** * Initializes a new <tt>MuteButton</tt> instance which is to mute the audio * stream to a specific <tt>CallPeer</tt>. * - * @param call the <tt>Call</tt> to be associated with - * the new instance and to have the audio stream sent to muted + * @param call the <tt>Call</tt> to be associated with the new instance and + * to have the audio stream sent to muted */ public MuteButton(Call call) { @@ -42,64 +43,41 @@ public class MuteButton /** * Initializes a new <tt>MuteButton</tt> instance which is to mute the audio - * stream to a specific <tt>CallPeer</tt>. + * stream to a specific <tt>Call</tt>. * - * @param call the <tt>Call</tt> to be associated with - * the new instance and to be put on/off hold upon performing - * its action. - * @param isFullScreenMode indicates if this button will be used in a normal - * or full screen mode. - * @param isSelected indicates the initial state of this toggle button - - * selected or not. + * @param call the <tt>Call</tt> to be associated with the new instance and + * whose audio stream is to be muted upon performing its action + * @param fullScreen <tt>true</tt> if the new instance is to be used in + * full-screen UI; otherwise, <tt>false</tt> + * @param selected <tt>true</tt> if the new toggle button is to be initially + * selected; otherwise, <tt>false</tt> */ - public MuteButton(Call call, boolean isFullScreenMode, boolean isSelected) + public MuteButton(Call call, boolean fullScreen, boolean selected) { - this.call = call; - - if (isFullScreenMode) - { - this.setBgImage( - ImageLoader.getImage(ImageLoader.FULL_SCREEN_BUTTON_BG)); - this.setBgRolloverImage( - ImageLoader.getImage(ImageLoader.FULL_SCREEN_BUTTON_BG)); - this.setIconImage( - ImageLoader.getImage(ImageLoader.MUTE_BUTTON)); - this.setPressedImage( - ImageLoader.getImage(ImageLoader.FULL_SCREEN_BUTTON_BG_PRESSED)); - } - else - { - this.setBgImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); - this.setBgRolloverImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_BG)); - this.setIconImage( - ImageLoader.getImage(ImageLoader.MUTE_BUTTON)); - this.setPressedImage( - ImageLoader.getImage(ImageLoader.CALL_SETTING_BUTTON_PRESSED_BG)); - } - - this.addActionListener(this); - setToolTipText(GuiActivator.getResources().getI18NString( - "service.gui.MUTE_BUTTON_TOOL_TIP")); - setSelected(isSelected); + super( + call, + fullScreen, + selected, + ImageLoader.MUTE_BUTTON, + "service.gui.MUTE_BUTTON_TOOL_TIP"); } /** - * Mutes or unmutes call peers when the mute button is clicked. - * @param evt the <tt>ActionEvent</tt> that notified us of the action + * Mutes or unmutes the associated <tt>Call</tt> upon clicking this button. + * + * @param evt an <tt>ActionEvent</tt> which describes the specifics of the + * performed action + * @see AbstractCallToggleButton#actionPerformed(ActionEvent) */ public void actionPerformed(ActionEvent evt) { if (call != null) { OperationSetBasicTelephony telephony - = call.getProtocolProvider() - .getOperationSet(OperationSetBasicTelephony.class); + = call.getProtocolProvider().getOperationSet( + OperationSetBasicTelephony.class); - // Obtain the isSelected property before invoking setMute. - boolean isMuteSelected = isSelected(); - telephony.setMute(call, isMuteSelected); + telephony.setMute(call, isSelected()); } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/call/RecordButton.java b/src/net/java/sip/communicator/impl/gui/main/call/RecordButton.java new file mode 100644 index 0000000..371d828 --- /dev/null +++ b/src/net/java/sip/communicator/impl/gui/main/call/RecordButton.java @@ -0,0 +1,351 @@ +/* + * 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.gui.main.call; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.text.*; +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.impl.gui.*; +import net.java.sip.communicator.impl.gui.utils.*; +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.neomedia.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.resources.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.swing.*; + +/** + * The button that starts/stops the call recording. + * + * @author Dmitri Melnikov + */ +public class RecordButton + extends AbstractCallToggleButton +{ + /** + * Resource service. + */ + private static ResourceManagementService resources + = GuiActivator.getResources(); + + /** + * Configuration service. + */ + private static ConfigurationService configurationService + = GuiActivator.getConfigurationService(); + + /** + * The date format used in file names. + */ + private static SimpleDateFormat format + = new SimpleDateFormat("yyyy-MM-dd@HH.mm.ss"); + + /** + * <tt>true</tt> when the default directory to save calls to is set, + * <tt>false</tt> otherwise. + */ + private boolean isCallDirSet = false; + + /** + * The full filename of the saved call on the file system. + */ + private String callFilename; + + /** + * Input panel. + */ + private InputPanel inputPanel; + + /** + * Initializes a new <tt>RecordButton</tt> instance which is to record the + * audio stream. + * + * @param call the <tt>Call</tt> to be associated with the new instance and + * to have the audio stream recorded + */ + public RecordButton(Call call) + { + this(call, false, false); + } + + /** + * Initializes a new <tt>RecordButton</tt> instance which is to record the + * audio stream. + * + * @param call the <tt>Call</tt> to be associated with the new instance and + * to have its audio stream recorded + * @param fullScreen <tt>true</tt> if the new instance is to be used in + * full-screen UI; otherwise, <tt>false</tt> + * @param selected <tt>true</tt> if the new toggle button is to be initially + * selected; otherwise, <tt>false</tt> + */ + public RecordButton(Call call, boolean fullScreen, boolean selected) + { + super(call, fullScreen, selected, ImageLoader.RECORD_BUTTON, null); + + inputPanel = new InputPanel(); + + String toolTip + = resources.getI18NString("service.gui.RECORD_BUTTON_TOOL_TIP"); + String saveDir + = configurationService.getString(Recorder.SAVED_CALLS_PATH); + if (saveDir != null) + { + isCallDirSet = true; + toolTip = toolTip + " (" + saveDir + ")"; + } + setToolTipText(toolTip); + } + + /** + * Starts/stops the recording of the call when this button is pressed. + * + * @param evt the <tt>ActionEvent</tt> that notified us of the action + */ + public void actionPerformed(ActionEvent evt) + { + if (call != null) + { + OperationSetBasicTelephony<?> telephony = + call.getProtocolProvider().getOperationSet( + OperationSetBasicTelephony.class); + + boolean isRecordSelected = isSelected(); + // start recording + if (isRecordSelected) + { + // ask user input about where to save the call + if (!isCallDirSet) + { + int status = + JOptionPane + .showConfirmDialog( + this, + inputPanel, + resources + .getI18NString("plugin.callrecordingconfig.SAVE_CALL"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (status == JOptionPane.OK_OPTION) + { + callFilename = inputPanel.getSelectedFilename(); + configurationService.setProperty(Recorder.CALL_FORMAT, + inputPanel.getSelectedFormat()); + } + else + { + // user canceled the recording + setSelected(false); + return; + } + } + else + callFilename = createDefaultFilename(); + + telephony.startRecording(call, callFilename); + } + // stop recording + else + { + telephony.stopRecording(call); + JOptionPane.showMessageDialog(this, + resources.getI18NString( + "plugin.callrecordingconfig.CALL_SAVED_TO", new String[] + { callFilename }), + resources + .getI18NString("plugin.callrecordingconfig.CALL_SAVED"), + JOptionPane.INFORMATION_MESSAGE); + } + } + } + + /** + * Creates a full filename for the call by combining the directory, file + * prefix and extension. If the directory is <tt>null</tt> user's home + * directory is used. + * + * @return a full filename for the call + */ + private String createDefaultFilename() + { + String callsDir + = configurationService.getString(Recorder.SAVED_CALLS_PATH); + + // set to user's home when null + if (callsDir == null) + { + try + { + callsDir + = GuiActivator + .getFileAccessService() + .getDefaultDownloadDirectory() + .getAbsolutePath(); + } + catch (IOException ioex) + { + // Leave it in the current directory. + } + } + + String ext = configurationService.getString(Recorder.CALL_FORMAT); + + if (ext == null) + ext = SoundFileUtils.mp2; + + return + ((callsDir == null) ? "" : (callsDir + File.separator)) + + generateCallFilename(ext); + } + + /** + * Generates a file name for the call based on the current date. + * + * @param ext file extension + * @return the file name for the call + */ + private String generateCallFilename(String ext) + { + return format.format(new Date()) + "-confcall." + ext; + } + + private static class InputPanel + extends TransparentPanel + { + /** + * Call file chooser. + */ + private SipCommFileChooser callFileChooser; + + /** + * Selected file. + */ + private String selectedFilename; + + /** + * Format combo box. + */ + private JComboBox formatComboBox; + + /** + * Builds the panel. + */ + public InputPanel() + { + super(new BorderLayout()); + + initComponents(); + + callFileChooser = + GenericFileDialog.create(null, resources + .getI18NString("plugin.callrecordingconfig.SAVE_CALL"), + SipCommFileChooser.SAVE_FILE_OPERATION); + } + + /** + * Returns the selected file. + * + * @return the selected file + */ + public String getSelectedFilename() + { + return selectedFilename; + } + + /** + * Returns the selected format. + * + * @return the selected format + */ + public String getSelectedFormat() + { + return (String) formatComboBox.getSelectedItem(); + } + + /** + * Initializes the UI components. + */ + private void initComponents() + { + JPanel labelsPanel = new TransparentPanel(new GridLayout(2, 1)); + JLabel formatLabel = + new JLabel(resources + .getI18NString("plugin.callrecordingconfig.FORMAT")); + JLabel locationLabel = + new JLabel(resources + .getI18NString("plugin.callrecordingconfig.LOCATION")); + labelsPanel.add(formatLabel); + labelsPanel.add(locationLabel); + + JPanel dirPanel = + new TransparentPanel(new FlowLayout(FlowLayout.LEFT)); + final JTextField callDirTextField = new JTextField(); + callDirTextField.setPreferredSize(new Dimension(200, 30)); + callDirTextField.setEditable(false); + dirPanel.add(callDirTextField); + JButton callDirChooseButton = + new JButton(new ImageIcon(resources + .getImageInBytes("plugin.notificationconfig.FOLDER_ICON"))); + callDirChooseButton.setMinimumSize(new Dimension(30, 30)); + callDirChooseButton.setPreferredSize(new Dimension(30, 30)); + callDirChooseButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent arg0) + { + File selectedFile = callFileChooser.getFileFromDialog(); + + if (selectedFile != null) + { + selectedFilename = selectedFile.getAbsolutePath(); + callDirTextField.setText(selectedFilename); + } + } + }); + dirPanel.add(callDirChooseButton); + + JPanel comboPanel = + new TransparentPanel(new FlowLayout(FlowLayout.LEFT)); + JLabel emptyLabel = new JLabel(); + emptyLabel.setPreferredSize(new Dimension(30, 30)); + comboPanel.add(createFormatsComboBox()); + comboPanel.add(emptyLabel); + + JPanel valuesPanel = new TransparentPanel(new GridLayout(2, 1)); + valuesPanel.add(comboPanel); + valuesPanel.add(dirPanel); + + this.add(labelsPanel, BorderLayout.WEST); + this.add(valuesPanel, BorderLayout.CENTER); + } + + /** + * Creates a combo box with supported audio formats. + * + * @return a combo box with supported audio formats + */ + private Component createFormatsComboBox() + { + ComboBoxModel formatsComboBoxModel = + new DefaultComboBoxModel( + new String[] { + SoundFileUtils.mp2, + SoundFileUtils.wav, + SoundFileUtils.au, + SoundFileUtils.aif, + SoundFileUtils.gsm }); + + formatComboBox = new JComboBox(); + formatComboBox.setPreferredSize(new Dimension(200, 30)); + formatComboBox.setModel(formatsComboBoxModel); + return formatComboBox; + } + } +} diff --git a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java index 5bff83c..9424ae9 100644 --- a/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java +++ b/src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java @@ -30,6 +30,10 @@ import net.java.sip.communicator.util.swing.*; */ public class ImageLoader { + /** + * The <tt>Logger</tt> used by the <tt>ImageLoader</tt> class and its + * instances for logging output. + */ private static final Logger logger = Logger.getLogger(ImageLoader.class); /** @@ -447,6 +451,12 @@ public class ImageLoader = new ImageID("service.gui.buttons.MUTE_BUTTON"); /** + * A record button icon. The icon shown in the CallPeer panel. + */ + public static final ImageID RECORD_BUTTON + = new ImageID("service.gui.buttons.RECORD_BUTTON"); + + /** * A local video button icon. The icon shown in the CallPeer panel. */ public static final ImageID LOCAL_VIDEO_BUTTON @@ -498,13 +508,13 @@ public class ImageLoader * The security button: encrypted and SAS verified, encrypted only, * security off. */ - public static final ImageID ENCR_VERIFIED = new ImageID( - "service.gui.buttons.ENCR_VERIFIED"); + public static final ImageID ENCR_VERIFIED + = new ImageID("service.gui.buttons.ENCR_VERIFIED"); public static final ImageID ENCR = new ImageID("service.gui.buttons.ENCR"); - public static final ImageID ENCR_DISABLED = new ImageID( - "service.gui.buttons.ENCR_DISABLED"); + public static final ImageID ENCR_DISABLED + = new ImageID("service.gui.buttons.ENCR_DISABLED"); /** * The button icon of the Enter Full Screen command. The icon shown in the diff --git a/src/net/java/sip/communicator/impl/neomedia/CallRecordingConfigForm.java b/src/net/java/sip/communicator/impl/neomedia/CallRecordingConfigForm.java new file mode 100644 index 0000000..941e92a --- /dev/null +++ b/src/net/java/sip/communicator/impl/neomedia/CallRecordingConfigForm.java @@ -0,0 +1,266 @@ +/* + * 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.swing.*; + +import net.java.sip.communicator.service.configuration.*; +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.*; + +/** + * The saved calls management and configuration form. + * + * @author Dmitri Melnikov + */ +public class CallRecordingConfigForm + extends TransparentPanel + implements ActionListener +{ + /** + * Logger for this class. + */ + private final Logger logger + = Logger.getLogger(CallRecordingConfigForm.class); + + /** + * The resource service. + */ + private static final ResourceManagementService resources + = NeomediaActivator.getResources(); + + /** + * Directory where calls are stored. Default is SC_HOME/calls. + */ + private String savedCallsDir; + + /** + * Directory choose dialog. + */ + private SipCommFileChooser dirChooser; + + /** + * UI components. + */ + private JButton callDirChooseButton; + private JTextField callDirTextField; + private JComboBox formatsComboBox; + private JCheckBox saveCallsToCheckBox; + + /** + * Creates an instance of the <tt>CallConfigurationPanel</tt>. + * Checks for the <tt>SAVED_CALLS_PATH</tt> and sets it if it does not + * exist. + */ + public CallRecordingConfigForm() + { + super(new BorderLayout()); + + initComponents(); + loadValues(); + + dirChooser = + GenericFileDialog.create(null, resources + .getI18NString("plugin.callrecordingconfig.CHOOSE_DIR"), + SipCommFileChooser.LOAD_FILE_OPERATION); + ((JFileChooser) dirChooser) + .setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + } + + /** + * Loads values from the configuration and sets the UI components to these + * values. + */ + private void loadValues() + { + ConfigurationService configurationService + = NeomediaActivator.getConfigurationService(); + + String callFormat = configurationService.getString(Recorder.CALL_FORMAT); + formatsComboBox.setSelectedItem(callFormat == null ? SoundFileUtils.mp2 + : callFormat); + + savedCallsDir = configurationService.getString(Recorder.SAVED_CALLS_PATH); + saveCallsToCheckBox.setSelected(savedCallsDir != null); + callDirTextField.setText(savedCallsDir); + callDirTextField.setEnabled(saveCallsToCheckBox.isSelected()); + callDirChooseButton.setEnabled(saveCallsToCheckBox.isSelected()); + } + + /** + * Creates a panel with call management components. + */ + private void initComponents() + { + // labels panel + JPanel labelsPanel = new TransparentPanel(new GridLayout(2, 1)); + + JLabel formatsLabel = new JLabel( + resources.getI18NString("plugin.callrecordingconfig.SUPPORTED_FORMATS")); + saveCallsToCheckBox = + new SIPCommCheckBox(resources + .getI18NString("plugin.callrecordingconfig.SAVE_CALLS")); + saveCallsToCheckBox.addActionListener(this); + + labelsPanel.add(formatsLabel); + labelsPanel.add(saveCallsToCheckBox); + + // combo box panel + JPanel comboPanel = + new TransparentPanel(new FlowLayout(FlowLayout.LEFT)); + + JLabel emptyLabel = new JLabel(); + emptyLabel.setPreferredSize(new Dimension(30, 30)); + comboPanel.add(createFormatsComboBox()); + comboPanel.add(emptyLabel); + + // saved calls directory panel + JPanel callDirPanel = + new TransparentPanel(new FlowLayout(FlowLayout.LEFT)); + + callDirTextField = new JTextField(); + callDirTextField.setPreferredSize(new Dimension(200, 30)); + callDirTextField.addActionListener(this); + callDirPanel.add(callDirTextField); + + callDirChooseButton = + new JButton(new ImageIcon(resources + .getImageInBytes("plugin.notificationconfig.FOLDER_ICON"))); + callDirChooseButton.setMinimumSize(new Dimension(30,30)); + callDirChooseButton.setPreferredSize(new Dimension(30,30)); + callDirChooseButton.addActionListener(this); + callDirPanel.add(callDirChooseButton); + + // values panel + JPanel valuesPanel = new TransparentPanel(new GridLayout(2, 1)); + valuesPanel.add(comboPanel); + valuesPanel.add(callDirPanel); + + // main panel + JPanel mainPanel = new TransparentPanel(new BorderLayout()); + mainPanel.add(labelsPanel, BorderLayout.WEST); + mainPanel.add(valuesPanel, BorderLayout.CENTER); + + this.add(mainPanel, BorderLayout.NORTH); + } + + /** + * Creates a combo box with supported audio formats. + * + * @return a combo box with supported audio formats + */ + private Component createFormatsComboBox() + { + ComboBoxModel formatsComboBoxModel = + new DefaultComboBoxModel( + new String[] { + SoundFileUtils.mp2, + SoundFileUtils.wav, + SoundFileUtils.au, + SoundFileUtils.aif, + SoundFileUtils.gsm }); + + formatsComboBox = new JComboBox(); + formatsComboBox.setPreferredSize(new Dimension(200, 30)); + formatsComboBox.setModel(formatsComboBoxModel); + + formatsComboBox.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent event) + { + if (event.getStateChange() == ItemEvent.SELECTED) + NeomediaActivator + .getConfigurationService() + .setProperty(Recorder.CALL_FORMAT, event.getItem()); + } + }); + return formatsComboBox; + } + + /** + * Indicates that one of the contained in this panel components has + * performed an action. + * + * @param e the <tt>ActionEvent</tt> that notified us + */ + public void actionPerformed(ActionEvent e) + { + Object source = e.getSource(); + if (source == saveCallsToCheckBox) + { + boolean selected = saveCallsToCheckBox.isSelected(); + callDirTextField.setEnabled(selected); + callDirChooseButton.setEnabled(selected); + if (selected) + { + // set default directory + try + { + changeCallsDir( + NeomediaActivator + .getFileAccessService() + .getDefaultDownloadDirectory()); + } + catch (IOException ioex) + { + } + } + else + { + // remove default directory prop + NeomediaActivator + .getConfigurationService() + .setProperty(Recorder.SAVED_CALLS_PATH, null); + callDirTextField.setText(null); + } + } + else if (source == callDirChooseButton) + { + File newDir = dirChooser.getFileFromDialog(); + changeCallsDir(newDir); + } + else if (source == callDirTextField) + { + File newDir = new File(callDirTextField.getText()); + changeCallsDir(newDir); + } + } + + /** + * Sets the new directory for the saved calls to <tt>dir</tt>. + * + * @param dir the new chosen directory + * @return <tt>true</tt> if directory was changed successfully, + * <tt>false</tt> otherwise + */ + private boolean changeCallsDir(File dir) + { + if (dir != null && dir.isDirectory()) + { + savedCallsDir = dir.getAbsolutePath(); + callDirTextField.setText(savedCallsDir); + NeomediaActivator + .getConfigurationService() + .setProperty(Recorder.SAVED_CALLS_PATH, savedCallsDir); + + if (logger.isDebugEnabled()) + logger.debug("Calls directory changed to " + savedCallsDir); + return true; + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Calls directory not changed."); + return false; + } + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java index 2a45352..465098d 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaServiceImpl.java @@ -21,6 +21,7 @@ import net.java.sip.communicator.service.neomedia.format.*; * Implements <tt>MediaService</tt> for JMF. * * @author Lubomir Marinov + * @author Dmitri Melnikov */ public class MediaServiceImpl implements MediaService @@ -563,6 +564,26 @@ public class MediaServiceImpl } /** + * Creates a new <tt>Recorder</tt> instance that can be used to record a + * call which captures and plays back media using a specific + * <tt>MediaDevice</tt>. + * + * @param device the <tt>MediaDevice</tt> which is used for media capture + * and playback by the call to be recorded + * @return a new <tt>Recorder</tt> instance that can be used to record a + * call which captures and plays back media using the specified + * <tt>MediaDevice</tt> + * @see MediaService#createRecorder(MediaDevice) + */ + public Recorder createRecorder(MediaDevice device) + { + if (device instanceof AudioMixerMediaDevice) + return new RecorderImpl((AudioMixerMediaDevice) device); + else + return null; + } + + /** * Returns a {@link Map} that binds indicates whatever preferences this * media service implementation may have for the RTP payload type numbers * that get dynamically assigned to {@link MediaFormat}s with no static @@ -586,7 +607,6 @@ public class MediaServiceImpl dynamicPayloadTypePreferences.put(telephoneEvent, (byte)101); } - return dynamicPayloadTypePreferences; } } diff --git a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java index cdeb8de..9ae36bf 100644 --- a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java +++ b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java @@ -187,6 +187,23 @@ public class NeomediaActivator if (logger.isInfoEnabled()) logger.info("Audio Notifier Service ...[REGISTERED]"); + + // Call Recording + Dictionary<String, String> callRecordingProps + = new Hashtable<String, String>(); + callRecordingProps.put( + ConfigurationForm.FORM_TYPE, + ConfigurationForm.ADVANCED_TYPE); + bundleContext.registerService( + ConfigurationForm.class.getName(), + new LazyConfigurationForm( + CallRecordingConfigForm.class.getName(), + getClass().getClassLoader(), + null, + "plugin.callrecordingconfig.CALL_RECORDING_CONFIG", + 1100, + true), + callRecordingProps); } /** diff --git a/src/net/java/sip/communicator/impl/neomedia/RecorderImpl.java b/src/net/java/sip/communicator/impl/neomedia/RecorderImpl.java new file mode 100644 index 0000000..d388c5c --- /dev/null +++ b/src/net/java/sip/communicator/impl/neomedia/RecorderImpl.java @@ -0,0 +1,149 @@ +/* + * 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.io.*; + +import javax.media.*; +import javax.media.protocol.*; + +import net.java.sip.communicator.impl.neomedia.device.*; +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.neomedia.*; +import net.java.sip.communicator.util.*; + +/** + * The call recording implementation. + * Provides the capability to start and stop call recording. + * + * @author Dmitri Melnikov + */ +public class RecorderImpl + implements Recorder +{ + /** + * The <tt>Logger</tt> used by the <tt>RecorderImpl</tt> class and its + * instances for logging output. + */ + private static final Logger logger = Logger.getLogger(RecorderImpl.class); + + /** + * The <tt>MediaDeviceSession</tt> is used to create an output data source. + */ + private MediaDeviceSession deviceSession; + + /** + * <tt>DataSink</tt> used to save the output data. + */ + private DataSink sink; + + /** + * <tt>true</tt> if recording was started, <tt>false</tt> + * otherwise. + */ + private boolean recording = false; + + /** + * Constructs the <tt>RecorderImpl</tt> with the provided session. + * + * @param device device that can create a session that provides the output + * data source + */ + public RecorderImpl(AudioMixerMediaDevice device) + { + if (device == null) + throw new NullPointerException("device"); + + ConfigurationService configurationService + = NeomediaActivator.getConfigurationService(); + String format = configurationService.getString(Recorder.CALL_FORMAT); + + if (format == null) + format = SoundFileUtils.mp2; + deviceSession + = device.createRecordingSession(getContentDescriptor(format)); + } + + /** + * Starts the call recording. + * + * @param filename call filename, when <tt>null</tt> a default filename is + * used + */ + public void startRecording(String filename) + { + if (!recording) + { + if (filename == null) + throw new NullPointerException("filename"); + + DataSource outputDataSource = deviceSession.getOutputDataSource(); + + try + { + sink + = Manager.createDataSink( + outputDataSource, + new MediaLocator("file:" + filename)); + sink.open(); + sink.start(); + } + catch (NoDataSinkException ndsex) + { + logger.error("No datasink can be found", ndsex); + } + catch (IOException ioex) + { + logger.error("Writing to datasink failed", ioex); + } + + recording = true; + } + } + + /** + * Stops the call recording. + */ + public void stopRecording() + { + if (recording) + { + deviceSession.close(); + deviceSession = null; + + if (sink != null) + { + sink.close(); + sink = null; + } + + recording = false; + } + } + + /** + * Returns a content descriptor to create a recording session with. + * + * @param format the format that corresponding to the content descriptor + * @return content descriptor + */ + private ContentDescriptor getContentDescriptor(String format) + { + String type = FileTypeDescriptor.MPEG_AUDIO; + + if (SoundFileUtils.wav.equals(format)) + type = FileTypeDescriptor.WAVE; + else if (SoundFileUtils.gsm.equals(format)) + type = FileTypeDescriptor.GSM; + else if (SoundFileUtils.au.equals(format)) + type = FileTypeDescriptor.BASIC_AUDIO; + else if (SoundFileUtils.aif.equals(format)) + type = FileTypeDescriptor.AIFF; + + return new ContentDescriptor(type); + } +} diff --git a/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java b/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java index 6bb798b..e7a6362 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/AudioMixerMediaDevice.java @@ -184,7 +184,7 @@ public class AudioMixerMediaDevice * captured by this <tt>MediaDevice</tt> * @see AbstractMediaDevice#createOutputDataSource() */ - AudioMixingPushBufferDataSource createOutputDataSource() + public AudioMixingPushBufferDataSource createOutputDataSource() { return getAudioMixer().createOutputDataSource(); } @@ -205,6 +205,74 @@ public class AudioMixerMediaDevice return new MediaStreamMediaDeviceSession(deviceSession); } + public synchronized MediaDeviceSession createRecordingSession( + final ContentDescriptor contentDescriptor) + { + if (deviceSession == null) + deviceSession = new AudioMixerMediaDeviceSession(); + + return new MediaStreamMediaDeviceSession(deviceSession) + { + /** + * Starts a specific <tt>Processor</tt> if this + * <tt>MediaDeviceSession</tt> has been started and the specified + * <tt>Processor</tt> is not started. Does not check the + * <tt>MediaDirection</tt> of this session when starting. + * + * @param processor the <tt>Processor</tt> to start + */ + @Override + protected void startProcessorInAccordWithDirection( + Processor processor) + { + if (processor.getState() != Processor.Started) + { + processor.start(); + if (logger.isTraceEnabled()) + logger.trace("Started Processor with hashCode " + + processor.hashCode()); + } + } + + /** + * Overrides the method to set the processor's content descriptor + * to <tt>FileTypeDescriptor.MPEG_AUDIO</tt>. + * + * @param event the <tt>ControllerEvent</tt> specifying the + * <tt>Controller</tt> which is the source of the event and the very + * type of the event + */ + @Override + protected void processorControllerUpdate(ControllerEvent event) + { + super.processorControllerUpdate(event); + + if (event instanceof ConfigureCompleteEvent) + { + Processor processor = (Processor) event.getSourceController(); + + if (processor != null) + { + try + { + processor.setContentDescriptor(contentDescriptor); + } + catch (NotConfiguredError nce) + { + logger + .error( + "Failed to set ContentDescriptor to Processor.", + nce); + } + + if (format != null) + setProcessorFormat(processor, format); + } + } + } + }; + } + /** * Notifies all currently registered <tt>SimpleAudioLevelListener</tt>s * that our local media now has audio level <tt>level</tt>. diff --git a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java index 39cfcaf..6bdc0d1 100644 --- a/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java +++ b/src/net/java/sip/communicator/impl/neomedia/device/MediaDeviceSession.java @@ -1613,7 +1613,7 @@ public class MediaDeviceSession * * @param processor the <tt>Processor</tt> to start */ - private void startProcessorInAccordWithDirection(Processor processor) + protected void startProcessorInAccordWithDirection(Processor processor) { if (startedDirection.allowsSending() && (processor.getState() != Processor.Started)) diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java b/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java index b62a525..abe631f 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/OperationSetBasicTelephonyGibberishImpl.java @@ -11,10 +11,12 @@ import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; /** * A Gibberish implementation of a basic telephony operation set. + * * @author Yana Stamcheva */ public class OperationSetBasicTelephonyGibberishImpl diff --git a/src/net/java/sip/communicator/impl/protocol/gibberish/gibberish.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/gibberish/gibberish.provider.manifest.mf index ec9208e..dde4396 100644 --- a/src/net/java/sip/communicator/impl/protocol/gibberish/gibberish.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/gibberish/gibberish.provider.manifest.mf @@ -10,4 +10,5 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.configuration.event, net.java.sip.communicator.util, net.java.sip.communicator.service.protocol, - net.java.sip.communicator.service.protocol.event + net.java.sip.communicator.service.protocol.event, + net.java.sip.communicator.service.protocol.media diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java index d044ccd..d000403 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepositoryJabberImpl.java @@ -24,7 +24,8 @@ public class ActiveCallsRepositoryJabberImpl OperationSetBasicTelephonyJabberImpl> { /** - * logger of this class + * The <tt>Logger</tt> used by the <tt>ActiveCallsRepositoryJabberImpl</tt> + * class and its instances for logging output. */ private static final Logger logger = Logger.getLogger(ActiveCallsRepositoryJabberImpl.class); @@ -89,4 +90,18 @@ public class ActiveCallsRepositoryJabberImpl return null; } + + /** + * Creates and dispatches a <tt>CallEvent</tt> notifying registered + * listeners that an event with id <tt>eventID</tt> has occurred on + * <tt>sourceCall</tt>. + * + * @param eventID the ID of the event to dispatch + * @param sourceCall the call on which the event has occurred + * @see ActiveCallsRepository#fireCallEvent(int, Call) + */ + protected void fireCallEvent(int eventID, Call sourceCall) + { + parentOperationSet.fireCallEvent(eventID, sourceCall); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java index 37a89f0..aedaf5e 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicTelephonyJabberImpl.java @@ -11,6 +11,7 @@ import java.util.*; import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java index b4cff03..920a503 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetBasicTelephony.java @@ -11,10 +11,11 @@ import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; /** - * A mock implementation of a basic telephony opearation set + * A mock implementation of a basic telephony operation set * * @author Damian Minkov */ diff --git a/src/net/java/sip/communicator/impl/protocol/mock/mock.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/mock/mock.provider.manifest.mf index a751ec9..c7e72e2 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/mock.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/mock/mock.provider.manifest.mf @@ -8,5 +8,6 @@ Import-Package: net.java.sip.communicator.service.contactlist, org.osgi.framework, net.java.sip.communicator.util, net.java.sip.communicator.service.protocol, - net.java.sip.communicator.service.protocol.event -Export-Package: net.java.sip.communicator.impl.protocol.mock, + net.java.sip.communicator.service.protocol.event, + net.java.sip.communicator.service.protocol.media +Export-Package: net.java.sip.communicator.impl.protocol.mock diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepositorySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepositorySipImpl.java index 6a83b61..08cd155 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepositorySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepositorySipImpl.java @@ -12,7 +12,6 @@ import javax.sip.*; import javax.sip.header.*; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; /** @@ -273,4 +272,18 @@ public class ActiveCallsRepositorySipImpl return (peer == null)? null : peer.getCall(); } + + /** + * Creates and dispatches a <tt>CallEvent</tt> notifying registered + * listeners that an event with id <tt>eventID</tt> has occurred on + * <tt>sourceCall</tt>. + * + * @param eventID the ID of the event to dispatch + * @param sourceCall the call on which the event has occurred + * @see ActiveCallsRepository#fireCallEvent(int, Call) + */ + protected void fireCallEvent(int eventID, Call sourceCall) + { + parentOperationSet.fireCallEvent(eventID, sourceCall); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java index 2a4a7f1..531b15d 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -20,6 +20,7 @@ import javax.sip.message.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.service.protocol.media.*; import net.java.sip.communicator.util.*; /** diff --git a/src/net/java/sip/communicator/plugin/notificationconfiguration/SoundFilter.java b/src/net/java/sip/communicator/plugin/notificationconfiguration/SoundFilter.java index 9665eeb..3f113bd 100644 --- a/src/net/java/sip/communicator/plugin/notificationconfiguration/SoundFilter.java +++ b/src/net/java/sip/communicator/plugin/notificationconfiguration/SoundFilter.java @@ -7,9 +7,10 @@ package net.java.sip.communicator.plugin.notificationconfiguration; -import java.io.File; +import java.io.*; -import net.java.sip.communicator.util.swing.SipCommFileFilter; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.swing.*; /** * Filter to display only the sound files in the filechooser @@ -38,28 +39,7 @@ public class SoundFilter /* * Else, it tests if the exension is correct */ - String extension = Utils.getExtension(f); - if (extension != null) - { - if (extension.equals(Utils.wav) || - extension.equals(Utils.mid) || - extension.equals(Utils.mp2) || - extension.equals(Utils.mp3) || - extension.equals(Utils.mod) || - extension.equals(Utils.ogg) || - extension.equals(Utils.wma) || - extension.equals(Utils.au) || - extension.equals(Utils.ram)) - { - return true; - } - else - { - return false; - } - } - - return false; + return SoundFileUtils.isSoundFile(f); } /** * Method which describes, in the file chooser, the text representing the permit extension @@ -72,40 +52,3 @@ public class SoundFilter "*.wav, *.wma)"; } } - -/** - * class which defines the different permit extension file - * @author Alexandre Maillard - */ -class Utils -{ - /* - * Differents extension of a sound file - */ - public final static String wav = "wav"; - public final static String mid = "mid"; - public final static String mp2 = "mp2"; - public final static String mp3 = "mp3"; - public final static String mod = "mod"; - public final static String ram = "ram"; - public final static String wma = "wma"; - public final static String ogg = "ogg"; - public final static String au = "au"; - - /* - * Gets the file extension. - * @param File which wants the extension - * @return Return the extension as a String - */ - public static String getExtension(File f) - { - String ext = null; - String s = f.getName(); - int i = s.lastIndexOf('.'); - - if (i > 0 && i < s.length() - 1) - ext = s.substring(i+1).toLowerCase(); - - return ext; - } -} diff --git a/src/net/java/sip/communicator/service/neomedia/MediaService.java b/src/net/java/sip/communicator/service/neomedia/MediaService.java index 5a8c326..86a6ae5 100644 --- a/src/net/java/sip/communicator/service/neomedia/MediaService.java +++ b/src/net/java/sip/communicator/service/neomedia/MediaService.java @@ -152,6 +152,19 @@ public interface MediaService public ScreenDevice getDefaultScreenDevice(); /** + * Creates a new <tt>Recorder</tt> instance that can be used to record a + * call which captures and plays back media using a specific + * <tt>MediaDevice</tt>. + * + * @param device the <tt>MediaDevice</tt> which is used for media capture + * and playback by the call to be recorded + * @return a new <tt>Recorder</tt> instance that can be used to record a + * call which captures and plays back media using the specified + * <tt>MediaDevice</tt> + */ + public Recorder createRecorder(MediaDevice device); + + /** * Returns a {@link Map} that binds indicates whatever preferences the * media service implementation may have for the RTP payload type numbers * that get dynamically assigned to {@link MediaFormat}s with no static diff --git a/src/net/java/sip/communicator/service/neomedia/Recorder.java b/src/net/java/sip/communicator/service/neomedia/Recorder.java new file mode 100644 index 0000000..651dca9 --- /dev/null +++ b/src/net/java/sip/communicator/service/neomedia/Recorder.java @@ -0,0 +1,41 @@ +/* + * 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.service.neomedia; + + +/** + * The call recording interface. + * Provides the capability to start and stop call recording. + * + * @author Dmitri Melnikov + */ +public interface Recorder +{ + /** + * Configuration property for the full path to the directory with saved + * calls. + */ + public static final String SAVED_CALLS_PATH = + "net.java.sip.communicator.impl.neomedia.SAVED_CALLS_PATH"; + /** + * Configuration property format of the saved call. + */ + public static final String CALL_FORMAT = + "net.java.sip.communicator.impl.neomedia.CALL_FORMAT"; + + /** + * Starts the call recording. + * + * @param callFilename call filename + */ + public void startRecording(String callFilename); + + /** + * Stops the call recording. + */ + public void stopRecording(); +} diff --git a/src/net/java/sip/communicator/service/protocol/ActiveCallsRepository.java b/src/net/java/sip/communicator/service/protocol/ActiveCallsRepository.java index 11a1faf..a8a775d 100644 --- a/src/net/java/sip/communicator/service/protocol/ActiveCallsRepository.java +++ b/src/net/java/sip/communicator/service/protocol/ActiveCallsRepository.java @@ -19,28 +19,27 @@ import net.java.sip.communicator.util.*; * * @author Emil Ivov */ -public class ActiveCallsRepository<T extends Call, - U extends AbstractOperationSetBasicTelephony> +public abstract class ActiveCallsRepository<T extends Call, + U extends OperationSetBasicTelephony> extends CallChangeAdapter { /** * The <tt>Logger</tt> used by the <tt>ActiveCallsRepository</tt> * class and its instances for logging output. */ - private static final Logger logger = Logger - .getLogger(ActiveCallsRepository.class.getName()); + private static final Logger logger + = Logger.getLogger(ActiveCallsRepository.class); /** * A table mapping call ids against call instances. */ - private Hashtable<String, T> activeCalls - = new Hashtable<String, T>(); + private final Hashtable<String, T> activeCalls = new Hashtable<String, T>(); /** * The operation set that created us. Instance is mainly used for firing * events when necessary. */ - private final U parentOperationSet; + protected final U parentOperationSet; /** * Creates a new instance of this repository. @@ -72,7 +71,7 @@ public class ActiveCallsRepository<T extends Call, public void callStateChanged(CallChangeEvent evt) { if(evt.getEventType().equals(CallChangeEvent.CALL_STATE_CHANGE) - && evt.getNewValue().equals(CallState.CALL_ENDED)) + && evt.getNewValue().equals(CallState.CALL_ENDED)) { T sourceCall = this.activeCalls.remove(evt.getSourceCall().getCallID()); @@ -81,8 +80,7 @@ public class ActiveCallsRepository<T extends Call, logger.trace("Removing call " + sourceCall + " from the list of " + "active calls because it entered an ENDED state"); - this.parentOperationSet.fireCallEvent( - CallEvent.CALL_ENDED, sourceCall); + fireCallEvent(CallEvent.CALL_ENDED, sourceCall); } } @@ -112,4 +110,20 @@ public class ActiveCallsRepository<T extends Call, } } + /** + * Creates and dispatches a <tt>CallEvent</tt> notifying registered + * listeners that an event with id <tt>eventID</tt> has occurred on + * <tt>sourceCall</tt>. + * <p> + * TODO The method is ugly because it can be implemented if + * <tt>parentOperationSet</tt> is an + * <tt>AbstractOperationSetBasicTelephony</tt>. But after the move of the + * latter in the <tt>.service.protocol.media</tt> package, it is not visible + * here. + * </p> + * + * @param eventID the ID of the event to dispatch + * @param sourceCall the call on which the event has occurred. + */ + protected abstract void fireCallEvent(int eventID, Call sourceCall); } diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java index 31dcb05..2b32c99 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetBasicTelephony.java @@ -6,9 +6,10 @@ */ package net.java.sip.communicator.service.protocol; -import net.java.sip.communicator.service.protocol.event.*; -import java.util.*; import java.text.*; +import java.util.*; + +import net.java.sip.communicator.service.protocol.event.*; /** * An Operation Set defining all basic telephony operations such as conducting @@ -159,4 +160,19 @@ public interface OperationSetBasicTelephony<T extends ProtocolProviderService> * this operation set. */ public T getProtocolProvider(); + + /** + * Starts the recording of the <tt>Call</tt>. + * + * @param call the <tt>Call</tt> to start recording + * @param callFilename call filename + */ + public void startRecording(Call call, String callFilename); + + /** + * Stops the recording of the <tt>Call</tt>. + * + * @param call the <tt>Call</tt> to stop recording + */ + public void stopRecording(Call call); } diff --git a/src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicTelephony.java b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetBasicTelephony.java index f56dd79..feaaf06 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractOperationSetBasicTelephony.java +++ b/src/net/java/sip/communicator/service/protocol/media/AbstractOperationSetBasicTelephony.java @@ -4,10 +4,11 @@ * Distributable under LGPL license. * See terms of license at gnu.org. */ -package net.java.sip.communicator.service.protocol; +package net.java.sip.communicator.service.protocol.media; import java.util.*; +import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; @@ -21,16 +22,19 @@ import net.java.sip.communicator.util.*; * * @author Lubomir Marinov * @author Emil Ivov + * @author Dmitri Melnikov */ public abstract class AbstractOperationSetBasicTelephony <T extends ProtocolProviderService> implements OperationSetBasicTelephony<T> { /** - * Our class logger + * The <tt>Logger</tt> used by the + * <tt>AbstractOperationSetBasicTelephony</tt> class and its instances for + * logging output. */ - private static final Logger logger = - Logger.getLogger(AbstractOperationSetBasicTelephony.class); + private static final Logger logger + = Logger.getLogger(AbstractOperationSetBasicTelephony.class); /** * A list of listeners registered for call events. @@ -74,11 +78,8 @@ public abstract class AbstractOperationSetBasicTelephony logger.debug("Dispatching a CallEvent to " + listeners.size() + " listeners. event is: " + cEvent); - for (Iterator<CallListener> listenerIter - = listeners.iterator(); listenerIter.hasNext();) + for (CallListener listener : listeners) { - CallListener listener = listenerIter.next(); - switch (eventID) { case CallEvent.CALL_INITIATED: @@ -116,7 +117,7 @@ public abstract class AbstractOperationSetBasicTelephony * * @param call the <tt>Call</tt> whose mute state is to be set * @param mute <tt>true</tt> to mute the call streams being sent to - * <tt>peers</tt>; otherwise, <tt>false</tt> + * <tt>peers</tt>; otherwise, <tt>false</tt> */ public void setMute(Call call, boolean mute) { @@ -126,4 +127,26 @@ public abstract class AbstractOperationSetBasicTelephony * this implementation takes inspiration from them. */ } + + /** + * Starts the recording of the <tt>Call</tt>. + * + * @param call the <tt>Call</tt> to start recording + * @param callFilename call filename, when <tt>null</tt> a default filename + * is used + */ + public void startRecording(Call call, String callFilename) + { + ((MediaAwareCall<?, ?, ?>) call).startRecording(callFilename); + } + + /** + * Stops the recording of the <tt>Call</tt>. + * + * @param call the <tt>Call</tt> to stop recording + */ + public void stopRecording(Call call) + { + ((MediaAwareCall<?, ?, ?>) call).stopRecording(); + } } diff --git a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java index 5dbb3b6..e16f631 100644 --- a/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java +++ b/src/net/java/sip/communicator/service/protocol/media/MediaAwareCall.java @@ -79,6 +79,11 @@ public abstract class MediaAwareCall< private boolean mute = false; /** + * The <tt>Recorder</tt> used to record this call. + */ + private Recorder recorder; + + /** * Device used in call will be chosen according to <tt>MediaUseCase</tt>. */ protected MediaUseCase mediaUseCase = MediaUseCase.ANY; @@ -330,7 +335,7 @@ public abstract class MediaAwareCall< MediaDevice device = mediaService.getDefaultDevice(mediaType, mediaUseCase); - if (MediaType.AUDIO.equals(mediaType) && isConferenceFocus()) + if (MediaType.AUDIO.equals(mediaType)) { if (conferenceAudioMixer == null) { @@ -565,4 +570,35 @@ public abstract class MediaAwareCall< peer.removeVideoPropertyChangeListener(listener); } } + + /** + * Stops the recording of this call. + */ + public void stopRecording() + { + recorder.stopRecording(); + } + + /** + * Starts the recording of this call. + * @param callFilename call filename + */ + public void startRecording(String callFilename) + { + MediaService mediaService = ProtocolMediaActivator.getMediaService(); + recorder = + mediaService.createRecorder(getDefaultDevice(MediaType.AUDIO)); + + recorder.startRecording(callFilename); + } + + /** + * Returns the recorder used by this instance to record the call. + * + * @return call recorder + */ + public Recorder getRecorder() + { + return recorder; + } } diff --git a/src/net/java/sip/communicator/util/SoundFileUtils.java b/src/net/java/sip/communicator/util/SoundFileUtils.java new file mode 100644 index 0000000..ec49256 --- /dev/null +++ b/src/net/java/sip/communicator/util/SoundFileUtils.java @@ -0,0 +1,78 @@ +/* + * 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.util; + +import java.io.*; + +/** + * Defines the different permit extension file. + * + * @author Alexandre Maillard + * @author Dmitri Melnikov + */ +public class SoundFileUtils +{ + /** + * Different extension of a sound file + */ + public final static String wav = "wav"; + public final static String mid = "midi"; + public final static String mp2 = "mp2"; + public final static String mp3 = "mp3"; + public final static String mod = "mod"; + public final static String ram = "ram"; + public final static String wma = "wma"; + public final static String ogg = "ogg"; + public final static String gsm = "gsm"; + public final static String aif = "aiff"; + public final static String au = "au"; + + /** + * Checks whether this file is a sound file. + * + * @param f <tt>File</tt> to check + * @return <tt>true</tt> if it's a sound file, <tt>false</tt> otherwise + */ + public static boolean isSoundFile(File f) + { + String extension = SoundFileUtils.getExtension(f); + if (extension != null) + { + return extension.equals(SoundFileUtils.wav) || + extension.equals(SoundFileUtils.mid) || + extension.equals(SoundFileUtils.mp2) || + extension.equals(SoundFileUtils.mp3) || + extension.equals(SoundFileUtils.mod) || + extension.equals(SoundFileUtils.ogg) || + extension.equals(SoundFileUtils.wma) || + extension.equals(SoundFileUtils.gsm) || + extension.equals(SoundFileUtils.au) || + extension.equals(SoundFileUtils.ram); + } + return false; + } + + /** + * Gets the file extension. + * TODO: There are at least 2 other methods like this scattered around + * the SC code, we should move them all to util package. + * + * @param f which wants the extension + * @return Return the extension as a String + */ + private static String getExtension(File f) + { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) + ext = s.substring(i+1).toLowerCase(); + + return ext; + } +} |