Commits callRecording.patch and recordButton.png provided by Dmitri Melnikov on the dev mailing list in the thread "Call Recording".

cusax-fix
Lyubomir Marinov 15 years ago
parent 13dbadac72
commit 347408cc5b

@ -143,6 +143,7 @@ service.gui.buttons.EDIT_TOOLBAR_BUTTON_PRESSED=resources/images/impl/gui/button
service.gui.buttons.DIAL_BUTTON=resources/images/impl/gui/buttons/dialButton.png
service.gui.buttons.HOLD_BUTTON=resources/images/impl/gui/buttons/holdButton.png
service.gui.buttons.MUTE_BUTTON=resources/images/impl/gui/buttons/muteButton.png
service.gui.buttons.RECORD_BUTTON=resources/images/impl/gui/buttons/recordButton.png
service.gui.buttons.LOCAL_VIDEO_BUTTON=resources/images/impl/gui/buttons/localVideoButton.png
service.gui.buttons.TRANSFER_CALL_BUTTON=resources/images/impl/gui/buttons/transferCallButton.png
service.gui.buttons.SECURE_BUTTON_ON=resources/images/impl/gui/buttons/secureOn.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

@ -423,6 +423,7 @@ service.gui.ENTER_FULL_SCREEN_TOOL_TIP=Enter Fullscreen
service.gui.EXIT_FULL_SCREEN_TOOL_TIP=Exit Fullscreen
service.gui.HOLD_BUTTON_TOOL_TIP=Toggle Hold
service.gui.MUTE_BUTTON_TOOL_TIP=Toggle Mute
service.gui.RECORD_BUTTON_TOOL_TIP=Toggle Record
service.gui.LOCAL_VIDEO_BUTTON_TOOL_TIP=Toggle Video
service.gui.TRANSFER_BUTTON_TOOL_TIP=Transfer Call
service.gui.TRANSFER_TO=Transfer to...
@ -907,6 +908,17 @@ impl.media.configform.VIDEO=&Camera:
impl.neomedia.configform.AUDIO=Audio
impl.neomedia.configform.VIDEO=Video
# The callrecordingconfig plugin was never really committed into trunk and its
# ConfigurationForm was actually put in the neomedia bundle.
plugin.callrecordingconfig.CALL_RECORDING_CONFIG=Call Recording
plugin.callrecordingconfig.SAVE_CALLS=Save calls to:
plugin.callrecordingconfig.SAVE_CALL=Save call to...
plugin.callrecordingconfig.CHOOSE_DIR=Choose a directory...
plugin.callrecordingconfig.SUPPORTED_FORMATS=Supported formats:
plugin.callrecordingconfig.CALL_SAVED=Call saved successfully
plugin.callrecordingconfig.CALL_SAVED_TO=Call has been saved to {0}
plugin.callrecordingconfig.FORMAT=Format:
plugin.callrecordingconfig.LOCATION=Location:
# Security configuration form title
plugin.securityconfig.TITLE=Security

@ -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);
}

@ -86,6 +86,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.
*/
@ -206,6 +212,7 @@ private void init()
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 @@ private void init()
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 void enableButtons()
conferenceButton.setEnabled(true);
holdButton.setEnabled(true);
muteButton.setEnabled(true);
recordButton.setEnabled(true);
if (!isLastConference)
{

@ -9,11 +9,9 @@
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 @@
*
* @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 HoldButton(Call call)
* 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 void actionPerformed(ActionEvent evt)
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);
}
}
}

@ -8,32 +8,33 @@
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 MuteButton(Call call)
/**
* 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());
}
}
}

@ -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;
}
}
}

@ -30,6 +30,10 @@
*/
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);
/**
@ -446,6 +450,12 @@ public class ImageLoader
public static final ImageID MUTE_BUTTON
= 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.
*/
@ -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

@ -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;
}
}
}

@ -21,6 +21,7 @@
* Implements <tt>MediaService</tt> for JMF.
*
* @author Lubomir Marinov
* @author Dmitri Melnikov
*/
public class MediaServiceImpl
implements MediaService
@ -562,6 +563,26 @@ public ScreenDevice getDefaultScreenDevice()
return best;
}
/**
* 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
@ -586,7 +607,6 @@ public Map<MediaFormat, Byte> getDynamicPayloadTypePreferences()
dynamicPayloadTypePreferences.put(telephoneEvent, (byte)101);
}
return dynamicPayloadTypePreferences;
}
}

@ -187,6 +187,23 @@ public void start(BundleContext bundleContext)
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);
}
/**

@ -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);
}
}

@ -184,7 +184,7 @@ public void connect(DataSource captureDevice)
* captured by this <tt>MediaDevice</tt>
* @see AbstractMediaDevice#createOutputDataSource()
*/
AudioMixingPushBufferDataSource createOutputDataSource()
public AudioMixingPushBufferDataSource createOutputDataSource()
{
return getAudioMixer().createOutputDataSource();
}
@ -205,6 +205,74 @@ public synchronized MediaDeviceSession createSession()
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>.

@ -1613,7 +1613,7 @@ else if ((processor != null)
*
* @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))

@ -11,10 +11,12 @@
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

@ -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

@ -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 CallPeerJabberImpl findCallPeer(String sid)
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);
}
}

@ -11,6 +11,7 @@
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.*;

@ -11,10 +11,11 @@
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
*/

@ -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

@ -12,7 +12,6 @@
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 CallSipImpl findCall(String callID,
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);
}
}

@ -20,6 +20,7 @@
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.*;
/**

@ -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 boolean accept(File f)
/*
* 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 String getDescription()
"*.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;
}
}

@ -151,6 +151,19 @@ public MediaStream createMediaStream(StreamConnector connector,
*/
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

@ -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();
}

@ -19,28 +19,27 @@
*
* @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 void addCall(T 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 void callStateChanged(CallChangeEvent evt)
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 int getActiveCallCount()
}
}
/**
* 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);
}

@ -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 void hangupCallPeer(CallPeer peer)
* 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);
}

@ -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 @@
*
* @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 void fireCallEvent(int eventID, Call sourceCall)
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 void removeCallListener(CallListener listener)
*
* @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 void setMute(Call call, boolean mute)
* 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();
}
}

@ -78,6 +78,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>.
*/
@ -330,7 +335,7 @@ public MediaDevice getDefaultDevice(MediaType mediaType)
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 void removeVideoPropertyChangeListener(
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;
}
}

@ -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;
}
}
Loading…
Cancel
Save