diff --git a/build.xml b/build.xml
index da3e1745f..94ee922bd 100644
--- a/build.xml
+++ b/build.xml
@@ -1062,6 +1062,8 @@
prefix="net/java/sip/communicator/impl/media/protocol"/>
+
@@ -1095,6 +1097,8 @@
prefix="net/java/sip/communicator/impl/media/protocol"/>
+
@@ -1127,6 +1131,8 @@
prefix="net/java/sip/communicator/impl/media/protocol"/>
+
diff --git a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java
index a21dc6fed..419372979 100644
--- a/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java
+++ b/src/net/java/sip/communicator/impl/neomedia/NeomediaActivator.java
@@ -6,6 +6,7 @@
*/
package net.java.sip.communicator.impl.neomedia;
+import net.java.sip.communicator.service.audionotifier.*;
import net.java.sip.communicator.service.configuration.*;
import net.java.sip.communicator.service.fileaccess.*;
import net.java.sip.communicator.service.gui.*;
@@ -14,6 +15,8 @@
import net.java.sip.communicator.service.resources.*;
import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.impl.neomedia.notify.*;
+
import org.osgi.framework.*;
/**
@@ -122,6 +125,24 @@ public void start(BundleContext bundleContext)
//following property to make sure that it would accept java generated
//IPv6 addresses that contain address scope zones.
System.setProperty("gov.nist.core.STRIP_ADDR_SCOPES", "true");
+
+ // AudioNotify Service
+ AudioNotifierServiceImpl audioNotifier = new AudioNotifierServiceImpl(
+ mediaServiceImpl.getDeviceConfiguration());
+
+ audioNotifier.setMute(
+ !getConfigurationService()
+ .getBoolean(
+ "net.java.sip.communicator.impl.sound.isSoundEnabled",
+ true));
+
+ getBundleContext()
+ .registerService(
+ AudioNotifierService.class.getName(),
+ audioNotifier,
+ null);
+
+ logger.info("Audio Notifier Service ...[REGISTERED]");
}
/**
diff --git a/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf
index a2741606c..1c2f9cfef 100644
--- a/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf
+++ b/src/net/java/sip/communicator/impl/neomedia/neomedia.manifest.mf
@@ -38,6 +38,7 @@ Import-Package: org.osgi.framework,
gnu.java.zrtp.utils,
gnu.java.zrtp.zidfile
Export-Package: net.java.sip.communicator.service.neomedia,
+ net.java.sip.communicator.service.audionotifier,
net.java.sip.communicator.service.neomedia.device,
net.java.sip.communicator.service.neomedia.event,
net.java.sip.communicator.service.neomedia.format
diff --git a/src/net/java/sip/communicator/impl/neomedia/notify/AudioNotifierServiceImpl.java b/src/net/java/sip/communicator/impl/neomedia/notify/AudioNotifierServiceImpl.java
new file mode 100644
index 000000000..e44327d0a
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/neomedia/notify/AudioNotifierServiceImpl.java
@@ -0,0 +1,175 @@
+/*
+ * 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.notify;
+
+import java.net.*;
+import java.util.*;
+
+import net.java.sip.communicator.impl.neomedia.*;
+import net.java.sip.communicator.impl.neomedia.device.*;
+import net.java.sip.communicator.service.audionotifier.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * The implementation of the AudioNotifierService.
+ *
+ * @author Yana Stamcheva
+ */
+public class AudioNotifierServiceImpl
+ implements AudioNotifierService,
+ PropertyChangeListener
+{
+ private static final Map audioClips =
+ new HashMap();
+
+ private boolean isMute;
+
+ /**
+ * Device config to look for notify device.
+ */
+ private DeviceConfiguration deviceConfiguration;
+
+ /**
+ * Creates audio notify service.
+ * @param deviceConfiguration the device configuration.
+ */
+ public AudioNotifierServiceImpl(DeviceConfiguration deviceConfiguration)
+ {
+ this.deviceConfiguration = deviceConfiguration;
+ deviceConfiguration.addPropertyChangeListener(this);
+ }
+
+ /**
+ * Creates an SCAudioClip from the given URI and adds it to the list of
+ * available audio-s.
+ *
+ * @param uri the path where the audio file could be found
+ */
+ public SCAudioClipImpl createAudio(String uri)
+ {
+ SCAudioClipImpl audioClip;
+
+ synchronized (audioClips)
+ {
+ if(audioClips.containsKey(uri))
+ {
+ audioClip = audioClips.get(uri);
+ }
+ else
+ {
+ URL url =
+ NeomediaActivator.getResources().getSoundURLForPath(uri);
+
+ if (url == null)
+ {
+ // Not found by the class loader. Perhaps it's a local file.
+ try
+ {
+ url = new URL(uri);
+ }
+ catch (MalformedURLException e)
+ {
+ //logger.error("The given uri could not be parsed.", e);
+ return null;
+ }
+ }
+
+ try
+ {
+ if(getDeviceConfiguration().getAudioSystem().equals(
+ DeviceConfiguration.AUDIO_SYSTEM_JAVASOUND))
+ {
+ audioClip = new JMFAudioClipImpl(url, this);
+ }
+ else if(getDeviceConfiguration().getAudioSystem().equals(
+ DeviceConfiguration.AUDIO_SYSTEM_PORTAUDIO))
+ {
+ audioClip = new PortAudioClipImpl(url, this);
+ }
+ else
+ return null;
+ }
+ catch (Throwable e)
+ {
+ // Cannot create audio to play
+ return null;
+ }
+
+ audioClips.put(uri, audioClip);
+ }
+ }
+
+ return audioClip;
+ }
+
+ /**
+ * Removes the given audio from the list of available audio clips.
+ *
+ * @param audioClip the audio to destroy
+ */
+ public void destroyAudio(SCAudioClip audioClip)
+ {
+ synchronized (audioClips) {
+ audioClips.remove(audioClip);
+ }
+ }
+
+ /**
+ * Enables or disables the sound in the application. If FALSE, we try to
+ * restore all looping sounds if any.
+ *
+ * @param isMute when TRUE disables the sound, otherwise enables the sound.
+ */
+ public void setMute(boolean isMute)
+ {
+ this.isMute = isMute;
+
+ for (SCAudioClipImpl audioClip : audioClips.values())
+ {
+ if (isMute)
+ {
+ audioClip.internalStop();
+ }
+ else if (audioClip.isLooping())
+ {
+ audioClip.playInLoop(audioClip.getLoopInterval());
+ }
+ }
+ }
+
+ /**
+ * Returns TRUE if the sound is currently disabled, FALSE otherwise.
+ * @return TRUE if the sound is currently disabled, FALSE otherwise
+ */
+ public boolean isMute()
+ {
+ return isMute;
+ }
+
+ /**
+ * The device configuration.
+ *
+ * @return the deviceConfiguration
+ */
+ public DeviceConfiguration getDeviceConfiguration()
+ {
+ return deviceConfiguration;
+ }
+
+ /**
+ * Listens for changes in notify device
+ * @param evt the event that notify device has changed.
+ */
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ if(evt.getPropertyName().equals(
+ DeviceConfiguration.AUDIO_NOTIFY_DEVICE))
+ {
+ audioClips.clear();
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/neomedia/notify/JMFAudioClipImpl.java b/src/net/java/sip/communicator/impl/neomedia/notify/JMFAudioClipImpl.java
new file mode 100644
index 000000000..d6422d58d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/neomedia/notify/JMFAudioClipImpl.java
@@ -0,0 +1,201 @@
+/*
+ * 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.notify;
+
+import java.applet.*;
+import java.awt.event.*;
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.security.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.service.audionotifier.*;
+
+/**
+ * Implementation of SCAudioClip.
+ *
+ * @author Yana Stamcheva
+ */
+public class JMFAudioClipImpl
+ extends SCAudioClipImpl
+ implements ActionListener
+
+{
+ private static Constructor acConstructor = null;
+
+ private final Timer playAudioTimer = new Timer(1000, null);
+
+ private final AudioClip audioClip;
+
+ private final AudioNotifierService audioNotifier;
+
+ /**
+ * Creates the audio clip and initialize the listener used from the
+ * loop timer.
+ *
+ * @param url the url pointing to the audio file
+ * @param audioNotifier the audio notify service
+ * @throws IOException cannot audio clip with supplied url.
+ */
+ public JMFAudioClipImpl(URL url, AudioNotifierService audioNotifier)
+ throws IOException
+ {
+ this.audioClip = createAppletAudioClip(url.openStream());
+ this.audioNotifier = audioNotifier;
+
+ this.playAudioTimer.addActionListener(this);
+ }
+
+ /**
+ * Plays this audio.
+ */
+ public void play()
+ {
+ if ((audioClip != null) && !audioNotifier.isMute())
+ audioClip.play();
+ }
+
+ /**
+ * Plays this audio in loop.
+ *
+ * @param interval the loop interval
+ */
+ public void playInLoop(int interval)
+ {
+ if(!audioNotifier.isMute())
+ {
+ if(interval == 0)
+ audioClip.loop();
+ else
+ {
+ //first play the audio and then start the timer and wait
+ audioClip.play();
+ playAudioTimer.setDelay(interval);
+ playAudioTimer.setRepeats(true);
+
+ playAudioTimer.start();
+ }
+ }
+
+ setLoopInterval(interval);
+ setIsLooping(true);
+ }
+
+ /**
+ * Stops this audio.
+ */
+ public void stop()
+ {
+ if (audioClip != null)
+ audioClip.stop();
+
+ if (isLooping())
+ {
+ playAudioTimer.stop();
+ setIsLooping(false);
+ }
+ }
+
+ /**
+ * Stops this audio without setting the isLooping property in the case of
+ * a looping audio. The AudioNotifier uses this method to stop the audio
+ * when setMute(true) is invoked. This allows us to restore all looping
+ * audios when the sound is restored by calling setMute(false).
+ */
+ public void internalStop()
+ {
+ if (audioClip != null)
+ audioClip.stop();
+
+ if (isLooping())
+ playAudioTimer.stop();
+ }
+
+ /**
+ * Creates an AppletAudioClip.
+ *
+ * @param inputstream the audio input stream
+ * @throws IOException
+ */
+ private static AudioClip createAppletAudioClip(InputStream inputstream)
+ throws IOException
+ {
+ if (acConstructor == null)
+ {
+ try
+ {
+ acConstructor
+ = AccessController.doPrivileged(
+ new PrivilegedExceptionAction>()
+ {
+ public Constructor run()
+ throws ClassNotFoundException,
+ NoSuchMethodException,
+ SecurityException
+ {
+ return createAcConstructor();
+ }
+ });
+ }
+ catch (PrivilegedActionException paex)
+ {
+ throw
+ new IOException(
+ "Failed to get AudioClip constructor: "
+ + paex.getException());
+ }
+ }
+
+ try
+ {
+ return acConstructor.newInstance(inputstream);
+ }
+ catch (Exception ex)
+ {
+ throw new IOException("Failed to construct the AudioClip: " + ex);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Constructor createAcConstructor()
+ throws ClassNotFoundException,
+ NoSuchMethodException,
+ SecurityException
+ {
+ Class> class1;
+ try
+ {
+ class1
+ = Class.forName(
+ "com.sun.media.sound.JavaSoundAudioClip",
+ true,
+ ClassLoader.getSystemClassLoader());
+ }
+ catch (ClassNotFoundException cnfex)
+ {
+ class1
+ = Class.forName("sun.audio.SunAudioClip", true, null);
+ }
+ return
+ (Constructor) class1.getConstructor(InputStream.class);
+ }
+
+ /**
+ * Plays an audio clip. Used in the playAudioTimer to play an audio in loop.
+ * @param e the event.
+ */
+ public void actionPerformed(ActionEvent e)
+ {
+ if (audioClip != null)
+ {
+ audioClip.stop();
+ audioClip.play();
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java b/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java
new file mode 100644
index 000000000..47576eae9
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/neomedia/notify/PortAudioClipImpl.java
@@ -0,0 +1,170 @@
+/*
+ * 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.notify;
+
+import java.io.*;
+import java.net.*;
+
+import javax.sound.sampled.*;
+
+import net.java.sip.communicator.impl.media.protocol.portaudio.*;
+import net.java.sip.communicator.impl.media.protocol.portaudio.streams.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Implementation of SCAudioClip using PortAudio.
+ *
+ * @author Damian Minkov
+ */
+public class PortAudioClipImpl
+ extends SCAudioClipImpl
+{
+ private static final Logger logger
+ = Logger.getLogger(PortAudioClipImpl.class);
+
+ private final AudioNotifierServiceImpl audioNotifier;
+
+ private boolean started = false;
+
+ private final URL url;
+
+ /**
+ * Creates the audio clip and initialize the listener used from the
+ * loop timer.
+ *
+ * @param url the url pointing to the audio file
+ * @param audioNotifier the audio notify service
+ * @throws IOException cannot audio clip with supplied url.
+ */
+ public PortAudioClipImpl(URL url, AudioNotifierServiceImpl audioNotifier)
+ throws IOException
+ {
+ this.audioNotifier = audioNotifier;
+ this.url = url;
+ }
+
+ /**
+ * Plays this audio.
+ */
+ public void play()
+ {
+ if ((url != null) && !audioNotifier.isMute())
+ {
+ started = true;
+ new Thread(new PlayThread()).start();
+ }
+ }
+
+ /**
+ * Plays this audio in loop.
+ *
+ * @param interval the loop interval
+ */
+ public void playInLoop(int interval)
+ {
+ setLoopInterval(interval);
+ setIsLooping(true);
+
+ play();
+ }
+
+ /**
+ * Stops this audio.
+ */
+ public void stop()
+ {
+ internalStop();
+ setIsLooping(false);
+ }
+
+ /**
+ * Stops this audio without setting the isLooping property in the case of
+ * a looping audio. The AudioNotifier uses this method to stop the audio
+ * when setMute(true) is invoked. This allows us to restore all looping
+ * audios when the sound is restored by calling setMute(false).
+ */
+ public void internalStop()
+ {
+ if (url != null)
+ started = false;
+ }
+
+ private class PlayThread
+ implements Runnable
+ {
+ byte[] buffer = new byte[1024];
+ private OutputPortAudioStream portAudioStream = null;
+
+ public void run()
+ {
+ try
+ {
+ while(true)
+ {
+ AudioInputStream audioStream =
+ AudioSystem.getAudioInputStream(url);
+ AudioFormat audioStreamFormat = audioStream.getFormat();
+
+ if (portAudioStream == null)
+ {
+ int deviceIndex =
+ PortAudioUtils.getDeviceIndexFromLocator(
+ audioNotifier.getDeviceConfiguration().
+ getAudioNotifyDevice().getLocator());
+
+ portAudioStream = PortAudioManager.getInstance().
+ getOutputStream(
+ deviceIndex,
+ audioStreamFormat.getSampleRate(),
+ audioStreamFormat.getChannels(),
+ PortAudioUtils.getPortAudioSampleFormat(
+ audioStreamFormat.getSampleSizeInBits()));
+ portAudioStream.start();
+ }
+
+ if(!started)
+ {
+ portAudioStream.stop();
+ return;
+ }
+
+ while(audioStream.read(buffer) != -1)
+ {
+ portAudioStream.write(buffer);
+ }
+
+ if(!isLooping())
+ {
+ portAudioStream.stop();
+ break;
+ }
+ else
+ {
+ Thread.sleep(getLoopInterval());
+ }
+ }
+ }
+ catch (PortAudioException e)
+ {
+ logger.error(
+ "Cannot open portaudio device for notifications", e);
+ }
+ catch (IOException e)
+ {
+ logger.error("Error reading from audio resource", e);
+ }
+ catch (InterruptedException e)
+ {
+ logger.error("Cannot wait the interval between plays", e);
+ }
+ catch (UnsupportedAudioFileException e)
+ {
+ logger.error("Unknown file format", e);
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/neomedia/notify/SCAudioClipImpl.java b/src/net/java/sip/communicator/impl/neomedia/notify/SCAudioClipImpl.java
new file mode 100644
index 000000000..745b3d288
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/neomedia/notify/SCAudioClipImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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.notify;
+
+import net.java.sip.communicator.service.audionotifier.*;
+
+/**
+ * Common properties impl for SCAudioClip.
+ *
+ * @author Damian Minkov
+ */
+public abstract class SCAudioClipImpl
+ implements SCAudioClip
+{
+ private boolean isLooping;
+
+ private int loopInterval;
+
+ private boolean isInvalid;
+
+ /**
+ * Returns TRUE if this audio is invalid, FALSE otherwise.
+ *
+ * @return TRUE if this audio is invalid, FALSE otherwise
+ */
+ public boolean isInvalid()
+ {
+ return isInvalid;
+ }
+
+ /**
+ * Marks this audio as invalid or not.
+ *
+ * @param isInvalid TRUE to mark this audio as invalid, FALSE otherwise
+ */
+ public void setInvalid(boolean isInvalid)
+ {
+ this.setIsInvalid(isInvalid);
+ }
+
+ /**
+ * Returns TRUE if this audio is currently playing in loop, FALSE otherwise.
+ * @return TRUE if this audio is currently playing in loop, FALSE otherwise.
+ */
+ public boolean isLooping()
+ {
+ return isLooping;
+ }
+
+ /**
+ * Returns the loop interval if this audio is looping.
+ * @return the loop interval if this audio is looping
+ */
+ public int getLoopInterval()
+ {
+ return loopInterval;
+ }
+
+ /**
+ * @param isLooping the isLooping to set
+ */
+ public void setIsLooping(boolean isLooping)
+ {
+ this.isLooping = isLooping;
+ }
+
+ /**
+ * @param loopInterval the loopInterval to set
+ */
+ public void setLoopInterval(int loopInterval)
+ {
+ this.loopInterval = loopInterval;
+ }
+
+ /**
+ * @param isInvalid the isInvalid to set
+ */
+ public void setIsInvalid(boolean isInvalid)
+ {
+ this.isInvalid = isInvalid;
+ }
+
+ /**
+ * Stops this audio without setting the isLooping property in the case of
+ * a looping audio. The AudioNotifier uses this method to stop the audio
+ * when setMute(true) is invoked. This allows us to restore all looping
+ * audios when the sound is restored by calling setMute(false).
+ */
+ public abstract void internalStop();
+}