diff --git a/src/org/jitsi/impl/neomedia/device/AudioSystem.java b/src/org/jitsi/impl/neomedia/device/AudioSystem.java
index b60e21d233c2a49f0adf7187118fc8642e58d20c..70d1bfd5f34b9509055500c0f326c58d56b6953d 100644
--- a/src/org/jitsi/impl/neomedia/device/AudioSystem.java
+++ b/src/org/jitsi/impl/neomedia/device/AudioSystem.java
@@ -6,15 +6,10 @@
  */
 package org.jitsi.impl.neomedia.device;
 
-import java.io.*;
 import java.util.*;
 
 import javax.media.*;
-import javax.media.format.*;
 
-import org.jitsi.impl.neomedia.*;
-import org.jitsi.service.configuration.*;
-import org.jitsi.service.libjitsi.*;
 import org.jitsi.service.neomedia.*;
 
 public abstract class AudioSystem
diff --git a/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java b/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java
index f2a79c283805d250bb596c9f955326c28de663c9..a4131ddfee8ce0590a692837749f4b4e517be293 100644
--- a/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java
+++ b/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java
@@ -9,6 +9,7 @@
 import java.beans.*;
 import java.net.*;
 import java.util.*;
+import java.util.concurrent.*;
 
 import javax.media.*;
 
@@ -19,30 +20,40 @@
 import org.jitsi.service.resources.*;
 
 /**
- * The implementation of the AudioNotifierService.
+ * The implementation of <tt>AudioNotifierService</tt>.
  *
  * @author Yana Stamcheva
+ * @author Lyubomir Marinov
  */
 public class AudioNotifierServiceImpl
     implements AudioNotifierService,
                PropertyChangeListener
 {
     /**
-     * Map of different audio clips.
+     * The cache of <tt>SCAudioClip</tt> instances which we may reuse. The reuse
+     * is complex because a <tt>SCAudioClip</tt> may be used by a single user at
+     * a time. 
      */
-    private static final Map<AudioClipsKey, AbstractSCAudioClip> audioClips
-        = new HashMap<AudioClipsKey, AbstractSCAudioClip>();
+    private Map<AudioKey, SCAudioClip> audios;
 
     /**
-     * If the sound is currently disabled.
+     * The <tt>Object</tt> which synchronizes the access to {@link #audios}.
      */
-    private boolean isMute;
+    private final Object audiosSyncRoot = new Object();
 
     /**
-     * Device config to look for notify device.
+     * The <tt>DeviceConfiguration</tt> which provides information about the
+     * notify and playback devices on which this instance plays
+     * <tt>SCAudioClip</tt>s.
      */
     private final DeviceConfiguration deviceConfiguration;
 
+    /**
+     * The indicator which determined whether <tt>SCAudioClip</tt>s are to be
+     * played by this instance.
+     */
+    private boolean mute;
+
     /**
      * Initializes a new <tt>AudioNotifierServiceImpl</tt> instance.
      */
@@ -53,7 +64,26 @@ public AudioNotifierServiceImpl()
                 .getMediaServiceImpl()
                     .getDeviceConfiguration();
 
-        deviceConfiguration.addPropertyChangeListener(this);
+        this.deviceConfiguration.addPropertyChangeListener(this);
+    }
+
+    /**
+     * Checks whether the playback and notification configuration
+     * share the same device.
+     * @return are audio out and notifications using the same device.
+     */
+    public boolean audioOutAndNotificationsShareSameDevice()
+    {
+        AudioSystem audioSystem = getDeviceConfiguration().getAudioSystem();
+        CaptureDeviceInfo notify
+            = audioSystem.getDevice(AudioSystem.NOTIFY_INDEX);
+        CaptureDeviceInfo playback
+            = audioSystem.getDevice(AudioSystem.PLAYBACK_INDEX);
+
+        return
+            (notify == null)
+                ? (playback == null)
+                : notify.getLocator().equals(playback.getLocator());
     }
 
     /**
@@ -63,7 +93,7 @@ public AudioNotifierServiceImpl()
      * @param uri the path where the audio file could be found
      * @return a newly created <tt>SCAudioClip</tt> from <tt>uri</tt>
      */
-    public AbstractSCAudioClip createAudio(String uri)
+    public SCAudioClip createAudio(String uri)
     {
         return createAudio(uri, false);
     }
@@ -76,18 +106,23 @@ public AbstractSCAudioClip createAudio(String uri)
      * @param playback use or not the playback device.
      * @return a newly created <tt>SCAudioClip</tt> from <tt>uri</tt>
      */
-    public AbstractSCAudioClip createAudio(String uri, boolean playback)
+    public SCAudioClip createAudio(String uri, boolean playback)
     {
-        AbstractSCAudioClip audioClip;
+        SCAudioClip audio;
 
-        synchronized (audioClips)
+        synchronized (audiosSyncRoot)
         {
-            AudioClipsKey key = new AudioClipsKey(uri, playback);
-            if(audioClips.containsKey(key))
-            {
-                audioClip = audioClips.get(key);
-            }
-            else
+            final AudioKey key = new AudioKey(uri, playback);
+
+            /*
+             * While we want to reuse the SCAudioClip instances, they may be
+             * used by a single user at a time. That's why we'll forget about
+             * them while they are in use and we'll reclaim them when they are
+             * no longer in use.
+             */
+            audio = (audios == null) ? null : audios.remove(key);
+
+            if (audio == null)
             {
                 ResourceManagementService resources
                     = LibJitsi.getResourceManagementService();
@@ -105,7 +140,6 @@ public AbstractSCAudioClip createAudio(String uri, boolean playback)
                     }
                     catch (MalformedURLException e)
                     {
-                        //logger.error("The given uri could not be parsed.", e);
                         return null;
                     }
                 }
@@ -116,16 +150,21 @@ public AbstractSCAudioClip createAudio(String uri, boolean playback)
                         = getDeviceConfiguration().getAudioSystem();
 
                     if (audioSystem == null)
-                        audioClip = new JavaSoundClipImpl(url, this);
+                    {
+                        audio = new JavaSoundClipImpl(url, this);
+                    }
                     else if (NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(
                             audioSystem.getLocatorProtocol()))
-                        audioClip = null;
+                    {
+                        audio = null;
+                    }
                     else
                     {
-                        audioClip
+                        audio
                             = new AudioSystemClipImpl(
                                     url,
-                                    this, audioSystem,
+                                    this,
+                                    audioSystem,
                                     playback);
                     }
                 }
@@ -133,62 +172,85 @@ else if (NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(
                 {
                     if (t instanceof ThreadDeath)
                         throw (ThreadDeath) t;
-                    // Cannot create audio to play
-                    return null;
+                    else
+                    {
+                        /*
+                         * Could not initialize a new SCAudioClip instance to be
+                         * played.
+                         */
+                        return null;
+                    }
                 }
-
-                audioClips.put(key, 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)
-        {
-            AudioClipsKey keyToRemove = null;
-            for(Map.Entry<AudioClipsKey, AbstractSCAudioClip> entry
-                : audioClips.entrySet())
+            /*
+             * Make sure that the SCAudioClip will be reclaimed for reuse when
+             * it is no longer in use.
+             */
+            if (audio != null)
             {
-                if(entry.getValue().equals(audioClip))
-                {
-                    keyToRemove = entry.getKey();
-                    break;
-                }
+                if (audios == null)
+                    audios = new HashMap<AudioKey, SCAudioClip>();
+
+                /*
+                 * We have to return in the Map which was active at the time the
+                 * SCAudioClip was initialized because it may have become
+                 * invalid if the playback or notify audio device changed.
+                 */
+                final Map<AudioKey, SCAudioClip> finalAudios = audios;
+                final SCAudioClip finalAudio = audio;
+
+                audio
+                    = new SCAudioClip()
+                    {
+                        @Override
+                        protected void finalize()
+                            throws Throwable
+                        {
+                            try
+                            {
+                                synchronized (audios)
+                                {
+                                    finalAudios.put(key, finalAudio);
+                                }
+                            }
+                            finally
+                            {
+                                super.finalize();
+                            }
+                        }
+
+                        public void play()
+                        {
+                            finalAudio.play();
+                        }
+
+                        public void play(
+                                int loopInterval,
+                                Callable<Boolean> loopCondition)
+                        {
+                            finalAudio.play(loopInterval, loopCondition);
+                        }
+
+                        public void stop()
+                        {
+                            finalAudio.stop();
+                        }
+                    };
             }
-
-            audioClips.remove(keyToRemove);
         }
+
+        return audio;
     }
 
     /**
-     * Enables or disables the sound in the application. If FALSE, we try to
-     * restore all looping sounds if any.
+     * The device configuration.
      *
-     * @param isMute when TRUE disables the sound, otherwise enables the sound.
+     * @return the deviceConfiguration
      */
-    public void setMute(boolean isMute)
+    public DeviceConfiguration getDeviceConfiguration()
     {
-        this.isMute = isMute;
-
-        for (AbstractSCAudioClip audioClip : audioClips.values())
-        {
-            if (isMute)
-            {
-                audioClip.internalStop();
-            }
-            else if (audioClip.isLooping())
-            {
-                // TODO Auto-generated method stub
-            }
-        }
+        return deviceConfiguration;
     }
 
     /**
@@ -197,98 +259,96 @@ else if (audioClip.isLooping())
      */
     public boolean isMute()
     {
-        return isMute;
-    }
-
-    /**
-     * The device configuration.
-     *
-     * @return the deviceConfiguration
-     */
-    public DeviceConfiguration getDeviceConfiguration()
-    {
-        return deviceConfiguration;
+        return mute;
     }
 
     /**
      * Listens for changes in notify device
-     * @param event the event that notify device has changed.
+     * @param ev the event that notify device has changed.
      */
-    public void propertyChange(PropertyChangeEvent event)
+    public void propertyChange(PropertyChangeEvent ev)
     {
-        if (DeviceConfiguration.AUDIO_NOTIFY_DEVICE.equals(
-                event.getPropertyName()))
+        String propertyName = ev.getPropertyName();
+
+        if (DeviceConfiguration.AUDIO_NOTIFY_DEVICE.equals(propertyName)
+                || DeviceConfiguration.AUDIO_PLAYBACK_DEVICE.equals(
+                        propertyName))
         {
-            audioClips.clear();
+            synchronized (audiosSyncRoot)
+            {
+                /*
+                 * Make sure that the currently referenced SCAudioClips will not
+                 * be reclaimed.
+                 */
+                audios = null;
+            }
         }
     }
 
     /**
-     * Checks whether the playback and notification configuration
-     * share the same device.
-     * @return are audio out and notifications using the same device.
+     * Enables or disables the sound in the application. If FALSE, we try to
+     * restore all looping sounds if any.
+     *
+     * @param mute when TRUE disables the sound, otherwise enables the sound.
      */
-    public boolean audioOutAndNotificationsShareSameDevice()
+    public void setMute(boolean mute)
     {
-        CaptureDeviceInfo notifyInfo =
-            getDeviceConfiguration().getAudioSystem()
-                .getDevice(AudioSystem.NOTIFY_INDEX);
-        CaptureDeviceInfo playbackInfo =
-            getDeviceConfiguration().getAudioSystem()
-                .getDevice(AudioSystem.PLAYBACK_INDEX);
-
-        if(notifyInfo != null && playbackInfo != null)
-        {
-            return notifyInfo.getLocator().equals(playbackInfo.getLocator());
-        }
+        this.mute = mute;
 
-        return false;
+        // TODO Auto-generated method stub
     }
 
     /**
-     * Key for clips.
+     * Implements the key of {@link AudioNotifierServiceImpl#audios}. Combines the
+     * <tt>uri</tt> of the <tt>SCAudioClip</tt> with the indicator which
+     * determines whether the <tt>SCAudioClip</tt> in question uses the playback
+     * or the notify audio device.
      */
-    private static class AudioClipsKey
+    private static class AudioKey
     {
         /**
-         * The uri.
+         * Is it playback?
          */
-        private String uri;
+        private final boolean playback;
 
         /**
-         * Is it playback.
+         * The uri.
          */
-        private boolean isPlayback;
+        final String uri;
 
         /**
-         * Constructs key.
-         * @param uri by uri
-         * @param playback and playback
+         * Initializes a new <tt>AudioKey</tt> instance.
+         *
+         * @param uri
+         * @param playback
          */
-        private AudioClipsKey(String uri, boolean playback)
+        private AudioKey(String uri, boolean playback)
         {
             this.uri = uri;
-            isPlayback = playback;
+            this.playback = playback;
         }
 
         @Override
         public boolean equals(Object o)
         {
-            AudioClipsKey that = (AudioClipsKey) o;
-
-            if(isPlayback != that.isPlayback)
-                return false;
-            if(uri != null ? !uri.equals(that.uri) : that.uri != null)
+            if (o == this)
+                return true;
+            if (o == null)
                 return false;
 
-            return true;
+            AudioKey that = (AudioKey) o;
+
+            return
+                (playback == that.playback)
+                    && ((uri == null)
+                            ? (that.uri == null)
+                            : uri.equals(that.uri));
         }
 
         @Override
         public int hashCode()
         {
-            return (uri != null ? uri.hashCode() : 0)
-                + (isPlayback ? 1 : 0);
+            return ((uri == null) ? 0 : uri.hashCode()) + (playback ? 1 : 0);
         }
     }
 }
diff --git a/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java b/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java
index 2d42a48b25b21a5fd5b89da9f0514258d7771714..dc67a062032b6fa0356a055347c24185433248c5 100644
--- a/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java
+++ b/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java
@@ -97,12 +97,17 @@ public Constructor<AudioClip> run()
     private final AudioClip audioClip;
 
     /**
-     * Creates the audio clip and initialize the listener used from the
-     * loop timer.
+     * Initializes a new <tt>JavaSoundClipImpl</tt> instance which is to play
+     * audio stored at a specific <tt>URL</tt> using
+     * <tt>java.applet.AudioClip</tt>.
      *
-     * @param url the url pointing to the audio file
-     * @param audioNotifier the audio notify service
-     * @throws IOException cannot audio clip with supplied url.
+     * @param url the <tt>URL</tt> at which the audio is stored and which the
+     * new instance is to load
+     * @param audioNotifier the <tt>AudioNotifierService</tt> which is
+     * initializing the new instance and whose <tt>mute</tt> property/state is
+     * to be monitored by the new instance
+     * @throws IOException if a <tt>java.applet.AudioClip</tt> could not be
+     * initialized or the audio at the specified <tt>url</tt> could not be read
      */
     public JavaSoundClipImpl(URL url, AudioNotifierService audioNotifier)
             throws IOException
@@ -114,9 +119,11 @@ public JavaSoundClipImpl(URL url, AudioNotifierService audioNotifier)
 
     /**
      * {@inheritDoc}
+     *
+     * Stops the <tt>java.applet.AudioClip</tt> wrapped by this instance.
      */
     @Override
-    public void internalStop()
+    protected void internalStop()
     {
         try
         {
@@ -131,6 +138,8 @@ public void internalStop()
 
     /**
      * {@inheritDoc}
+     *
+     * Plays the <tt>java.applet.AudioClip</tt> wrapped by this instance.
      */
     protected boolean runOnceInPlayThread()
     {
diff --git a/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java b/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java
index 161dea63851802e0bf1df3102d99b18dcf84f55b..b2d48e0e6406c2a2ca1eb2277ca08a98031b547d 100644
--- a/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java
+++ b/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java
@@ -10,7 +10,10 @@
 import java.util.concurrent.*;
 
 /**
- * An abstract base implementation of {@link SCAudioClip}. 
+ * An abstract base implementation of {@link SCAudioClip} which is provided in
+ * order to aid implementers by allowing them to extend
+ * <tt>AbstractSCAudioClip</tt> and focus on the task of playing actual audio
+ * once.
  *
  * @author Damian Minkov
  * @author Lyubomir Marinov
@@ -18,18 +21,59 @@
 public abstract class AbstractSCAudioClip
     implements SCAudioClip
 {
+    /**
+     * The thread pool used by the <tt>AbstractSCAudioClip</tt> instances in
+     * order to reduce the impact of thread creation/initialization.
+     */
+    private static ExecutorService executorService;
+
+    /**
+     * The <tt>AudioNotifierService</tt> which has initialized this instance.
+     * <tt>AbstractSCAudioClip</tt> monitors its <tt>mute</tt> property/state in
+     * order to silence the played audio as appropriate/necessary.
+     */
     protected final AudioNotifierService audioNotifier;
 
-    private boolean isInvalid;
+    private Runnable command;
 
-    private boolean isLooping;
+    /**
+     * The indicator which determines whether this instance was marked invalid.
+     */
+    private boolean invalid;
+
+    /**
+     * The indicator which determines whether this instance plays the audio it
+     * represents in a loop.
+     */
+    private boolean looping;
 
+    /**
+     * The interval of time in milliseconds between consecutive plays of this
+     * audio in a loop. If negative, this audio is played once only. If
+     * non-negative, this audio may still be played once only if the
+     * <tt>loopCondition</tt> specified to {@link #play(int, Callable)} is
+     * <tt>null</tt> or its invocation fails.
+     */
     private int loopInterval;
 
-    private boolean started = false;
-    
+    /**
+     * The indicator which determines whether the playback of this audio is
+     * started.
+     */
+    private boolean started;
+
+    /**
+     * The <tt>Object</tt> used for internal synchronization purposes which
+     * arise because this instance does the actual playback of audio in a
+     * separate thread.
+     */
     private final Object sync = new Object();
 
+    /**
+     * The <tt>URL</tt> of the audio to be played by this instance.
+     * <tt>AbstractSCAudioClip</tt> does not use it and just remembers it in
+     * order to make it available to extenders.
+     */
     protected final URL url;
 
     protected AbstractSCAudioClip(
@@ -40,29 +84,62 @@ protected AbstractSCAudioClip(
         this.audioNotifier = audioNotifier;
     }
 
+    /**
+     * Notifies this instance that its execution in its background/separate
+     * thread dedicated to the playback of this audio is about to start playing
+     * this audio for the first time. Regardless of whether this instance is to
+     * be played once or multiple times in a loop, the method is called once in
+     * order to allow extenders/implementers to perform one-time initialization
+     * before this audio starts playing. The <tt>AbstractSCAudioClip</tt>
+     * implementation does nothing.
+     */
     protected void enterRunInPlayThread()
     {
-        // TODO Auto-generated method stub
     }
 
+    /**
+     * Notifies this instance that its execution in its background/separate
+     * thread dedicated to the playback of this audio is about the start playing
+     * this audio once. If this audio is to be played in a loop, the method is
+     * invoked at the beginning of each iteration of the loop. Allows
+     * extenders/implementers to perform per-loop iteration initialization. The
+     * <tt>AbstractSCAudioClip</tt> implementation does nothing.
+     */
     protected void enterRunOnceInPlayThread()
     {
-        // TODO Auto-generated method stub
     }
 
+    /**
+     * Notifies this instance that its execution in its background/separate
+     * thread dedicated to the playback of this audio is about to stop playing
+     * this audio once. Regardless of whether this instance is to be played once
+     * or multiple times in a loop, the method is called once in order to allow
+     * extenders/implementers to perform one-time cleanup after this audio stops
+     * playing. The <tt>AbstractSCAudioClip</tt> implementation does nothing.
+     */
     protected void exitRunInPlayThread()
     {
-        // TODO Auto-generated method stub
     }
 
+    /**
+     * Notifies this instance that its execution in its background/separate
+     * thread dedicated to the playback of this audio is about to stop playing
+     * this audio. If this audio is to be played in a loop, the method is called
+     * at the end of each iteration of the loop. Allows extenders/implementers
+     * to perform per-loop iteraction cleanup. The <tt>AbstractSCAudioClip</tt>
+     * implementation does nothing.
+     */
     protected void exitRunOnceInPlayThread()
     {
-        // TODO Auto-generated method stub
     }
 
     /**
-     * Returns the loop interval if this audio is looping.
-     * @return the loop interval if this audio is looping
+     * Gets the interval of time in milliseconds between consecutive plays of
+     * this audio.
+     *
+     * @return the interval of time in milliseconds between consecutive plays of
+     * this audio. If negative, this audio will not be played in a loop and will
+     * be played once only.
      */
     public int getLoopInterval()
     {
@@ -75,44 +152,84 @@ public int getLoopInterval()
      * 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()
+    protected void internalStop()
     {
+        boolean interrupted = false;
+
         synchronized (sync) 
         {
-            if (url != null && started) 
+            started = false;
+            sync.notifyAll();
+
+            while (command != null) 
             {
-                started = false;
-                sync.notifyAll();
+                try
+                {
+                    /*
+                     * Technically, we do not need a timeout. If a notifyAll()
+                     * is not called to wake us up, then we will likely already
+                     * be in trouble. Anyway, use a timeout just in case.
+                     */
+                    sync.wait(500);
+                }
+                catch (InterruptedException ie)
+                {
+                    interrupted = true;
+                }
             }
         }
+
+        if (interrupted)
+            Thread.currentThread().interrupt();
     }
 
     /**
-     * Returns TRUE if this audio is invalid, FALSE otherwise.
+     * Determines whether this instance is invalid. <tt>AbstractSCAudioClip</tt>
+     * does not use the <tt>invalid</tt> property/state of this instance and
+     * merely remembers the value which was set on it by
+     * {@link #setInvalid(boolean)}. The default value is <tt>false</tt> i.e.
+     * this instance is valid by default.
      *
-     * @return TRUE if this audio is invalid, FALSE otherwise
+     * @return <tt>true</tt> if this instance is invalid; otherwise,
+     * <tt>false</tt>
      */
     public boolean isInvalid()
     {
-        return isInvalid;
+        return invalid;
     }
 
     /**
-     * Returns TRUE if this audio is currently playing in loop, FALSE otherwise.
-     * @return TRUE if this audio is currently playing in loop, FALSE otherwise.
+     * Determines whether this instance plays the audio it represents in a loop.
+     *
+     * @param <tt>true</tt> if this instance plays the audio it represents in a
+     * loop; <tt>false</tt>, otherwise
      */
     public boolean isLooping()
     {
-        return isLooping;
+        return looping;
     }
 
+    /**
+     * Determines whether this audio is started i.e. a <tt>play</tt> method was
+     * invoked and no subsequent <tt>stop</tt> has been invoked yet.
+     *
+     * @return <tt>true</tt> if this audio is started; otherwise, <tt>false</tt>
+     */
     protected boolean isStarted()
     {
-        return started;
+        synchronized (sync)
+        {
+            return started;
+        }
     }
 
     /**
      * {@inheritDoc}
+     *
+     * Delegates to {@link #play(int, Callable)} with <tt>loopInterval</tt>
+     * <tt>-1</tt> and <tt>loopCondition</tt> <tt>null</tt> in order to conform
+     * with the contract for the behavior of this method specified by the
+     * interface <tt>SCAudioClip</tt>.
      */
     public void play()
     {
@@ -127,101 +244,220 @@ public void play(int loopInterval, final Callable<Boolean> loopCondition)
         if ((loopInterval >= 0) && (loopCondition == null))
             loopInterval = -1;
 
-        setLoopInterval(loopInterval);
-        setIsLooping(loopInterval >= 0);
-
-        if ((url != null) && !audioNotifier.isMute())
+        synchronized (sync)
         {
-            started = true;
-            new Thread()
+            if (command != null)
+                return;
+
+            setLoopInterval(loopInterval);
+            setLooping(loopInterval >= 0);
+
+            /*
+             * We use a thread pool shared among all AbstractSCAudioClip
+             * instances in order to reduce the impact of thread
+             * creation/initialization.
+             */
+            ExecutorService executorService;
+
+            synchronized (AbstractSCAudioClip.class)
+            {
+                if (AbstractSCAudioClip.executorService == null)
+                {
+                    AbstractSCAudioClip.executorService
+                        = Executors.newCachedThreadPool();
+                }
+                executorService = AbstractSCAudioClip.executorService;
+            }
+
+            try
+            {
+                started = false;
+                command
+                    = new Runnable()
                     {
-                        @Override
                         public void run()
                         {
-                            runInPlayThread(loopCondition);
+                            try
+                            {
+                                synchronized (sync)
+                                {
+                                    /*
+                                     * We have to wait for
+                                     * play(int,Callable<Boolean>) to let go of
+                                     * sync i.e. be ready with setting up the
+                                     * whole AbstractSCAudioClip state;
+                                     * otherwise, this Runnable will most likely
+                                     * prematurely seize to exist. 
+                                     */
+                                    if (!equals(command))
+                                        return;
+                                }
+
+                                runInPlayThread(loopCondition);
+                            }
+                            finally
+                            {
+                                synchronized (sync)
+                                {
+                                    if (equals(command))
+                                    {
+                                        command = null;
+                                        started = false;
+                                        sync.notifyAll();
+                                    }
+                                }
+                            }
                         }
-                    }.start();
+                    };
+                executorService.execute(command);
+                started = true;
+            }
+            finally
+            {
+                if (!started)
+                    command = null;
+                sync.notifyAll();
+            }
         }
     }
 
+    /**
+     * Runs in a background/separate thread dedicated to the actual playback of
+     * this audio and plays this audio once or in a loop.
+     *
+     * @param loopCondition a <tt>Callback&lt;Boolean&gt;</tt> which represents
+     * the condition on which this audio will play more than once. If
+     * <tt>null</tt>, this audio will play once only. If an invocation of
+     * <tt>loopCondition</tt> throws a <tt>Throwable</tt>, this audio will
+     * discontinue playing.
+     */
     private void runInPlayThread(Callable<Boolean> loopCondition)
     {
         enterRunInPlayThread();
         try
         {
-            while (started)
+            boolean interrupted = false;
+
+            while (isStarted())
             {
-                enterRunOnceInPlayThread();
-                try
+                if (audioNotifier.isMute())
                 {
-                    if (!runOnceInPlayThread())
-                        break;
+                    /*
+                     * If the AudioNotifierService has muted the sounds, we will
+                     * have to really wait a bit in order to not fall into a
+                     * busy wait.
+                     */
+                    synchronized (sync)
+                    {
+                        try
+                        {
+                            sync.wait(500);
+                        }
+                        catch (InterruptedException ie)
+                        {
+                            interrupted = true;
+                        }
+                    }
                 }
-                finally
+                else
                 {
-                    exitRunOnceInPlayThread();
+                    enterRunOnceInPlayThread();
+                    try
+                    {
+                        if (!runOnceInPlayThread())
+                            break;
+                    }
+                    finally
+                    {
+                        exitRunOnceInPlayThread();
+                    }
                 }
 
-                if(isLooping())
+                if(!isLooping())
+                    break;
+
+                synchronized (sync)
                 {
-                    synchronized(sync)
-                    {
-                        if (started)
-                        {
-                            try
-                            {
-                                /*
-                                 * Only wait if longer than 0; otherwise, we
-                                 * will wait forever.
-                                 */
-                                if(getLoopInterval() > 0)
-                                    sync.wait(getLoopInterval());
-                            }
-                            catch (InterruptedException e)
-                            {
-                            }
-                        }
-                    }
+                    /*
+                     * We may have waited to acquire sync. Before beginning the
+                     * wait for loopInterval, make sure we should continue.
+                     */
+                    if (!isStarted())
+                        break;
 
-                    if (started)
+                    try
                     {
-                        if (loopCondition == null)
-                        {
-                            /*
-                             * The interface contract is that this audio plays
-                             * once only if the loopCondition is null.
-                             */
-                            break;
-                        }
-                        else
-                        {
-                            boolean loop = false;
+                        int loopInterval = getLoopInterval();
 
-                            try
-                            {
-                                loop = loopCondition.call();
-                            }
-                            catch (Throwable t)
-                            {
-                                if (t instanceof ThreadDeath)
-                                    throw (ThreadDeath) t;
-                            }
-                            if (!loop)
-                            {
-                                /*
-                                 * The loopCondition failed to evaluate to true
-                                 * so the loop will not continue.
-                                 */
-                                break;
-                            }
-                        }
+                        /*
+                         * XXX The value 0 means that this instance should loop
+                         * playing without waiting but it means infinity to
+                         * Object.wait(long).
+                         */
+                        if (loopInterval > 0)
+                            sync.wait(loopInterval);
+                    }
+                    catch (InterruptedException ie)
+                    {
+                        interrupted = true;
                     }
-                    else
-                        break;
                 }
-                else
+
+                /*
+                 * After this audio has been played once, loopCondition should
+                 * be consulted to approve each subsequent iteration of the
+                 * loop. Before invoking loopCondition which may take noticeable
+                 * time to execute, make sure that this instance has not been
+                 * stopped while it waited for loopInterval.
+                 */
+                if (!isStarted())
+                    break;
+
+                if (loopCondition == null)
+                {
+                    /*
+                     * The interface contract is that this audio plays once
+                     * only if the loopCondition is null.
+                     */
+                    break;
+                }
+
+                /*
+                 * The contract of the SCAudioClip interface with respect to
+                 * loopCondition is that the loop will continue only if
+                 * loopCondition successfully and explicitly evaluates to true.
+                 */
+                boolean loop = false;
+
+                try
+                {
+                    loop = loopCondition.call();
+                }
+                catch (Throwable t)
+                {
+                    if (t instanceof ThreadDeath)
+                        throw (ThreadDeath) t;
+
+                    /*
+                     * If loopCondition fails to successfully and explicitly
+                     * evaluate to true, this audio should seize to play in a
+                     * loop. Otherwise, there is a risk that whoever requested
+                     * this audio to be played in a loop and provided the
+                     * loopCondition will continue to play it forever.
+                     */
+                }
+                if (!loop)
+                {
+                    /*
+                     * The loopCondition failed to successfully and explicitly
+                     * evaluate to true so the loop will not continue.
+                     */
                     break;
+                }
             }
+
+            if (interrupted)
+                Thread.currentThread().interrupt();
         }
         finally
         {
@@ -229,40 +465,76 @@ private void runInPlayThread(Callable<Boolean> loopCondition)
         }
     }
 
-    protected abstract boolean runOnceInPlayThread();
-
     /**
-     * Marks this audio as invalid or not.
+     * Plays this audio once.
      *
-     * @param isInvalid TRUE to mark this audio as invalid, FALSE otherwise
+     * @return <tt>true</tt> if subsequent plays of this audio and,
+     * respectively, the method are to be invoked if this audio is to be played
+     * in a loop; otherwise, <tt>false</tt>. The value reflects an
+     * implementation-specific loop condition, is not dependent on
+     * <tt>loopInterval</tt> and <tt>loopCondition</tt> and is combined with the
+     * latter in order to determine whether there will be a subsequent iteration
+     * of the playback loop.
      */
-    public void setInvalid(boolean isInvalid)
-    {
-        this.setIsInvalid(isInvalid);
-    }
+    protected abstract boolean runOnceInPlayThread();
 
     /**
-     * @param isInvalid the isInvalid to set
+     * Sets the indicator which determines whether this instance is invalid.
+     * <tt>AbstractSCAudioClip</tt> does not use the <tt>invalid</tt>
+     * property/state of this instance and merely remembers the value which was
+     * set on it so that it can be retrieved by {@link #isInvalid()}. The
+     * default value is <tt>false</tt> i.e. this instance is valid by default.
+     *
+     * @param invalid <tt>true</tt> to mark this instance invalid or
+     * <tt>false</tt> to mark it valid 
      */
-    public void setIsInvalid(boolean isInvalid)
+    public void setInvalid(boolean invalid)
     {
-        this.isInvalid = isInvalid;
+        this.invalid = invalid;
     }
 
     /**
-     * @param isLooping the isLooping to set
+     * Sets the indicator which determines whether this audio is to play in a
+     * loop. Generally, public invocation of the method is not necessary because
+     * the looping is controlled by the <tt>loopInterval</tt> property of this
+     * instance and the <tt>loopInterval</tt> and <tt>loopCondition</tt>
+     * parameters of {@link #play(int, Callable)} anyway.
+     *
+     * @param <tt>true</tt> to mark this instance that it should play the audio
+     * it represents in a loop; otherwise, <tt>false</tt>
      */
-    public void setIsLooping(boolean isLooping)
+    public void setLooping(boolean looping)
     {
-        this.isLooping = isLooping;
+        synchronized (sync)
+        {
+            if (this.looping != looping)
+            {
+                this.looping = looping;
+                sync.notifyAll();
+            }
+        }
     }
 
     /**
-     * @param loopInterval the loopInterval to set
+     * Sets the interval of time in milliseconds between consecutive plays of
+     * this audio in a loop. If negative, this audio is played once only. If
+     * non-negative, this audio may still be played once only if the
+     * <tt>loopCondition</tt> specified to {@link #play(int, Callable)} is
+     * <tt>null</tt> or its invocation fails.
+     *
+     * @param loopInterval the interval of time in milliseconds between
+     * consecutive plays of this audio in a loop to be set on this instance
      */
     public void setLoopInterval(int loopInterval)
     {
-        this.loopInterval = loopInterval;
+        synchronized (sync)
+        {
+            if (this.loopInterval != loopInterval)
+            {
+                this.loopInterval = loopInterval;
+                sync.notifyAll();
+            }
+        }
     }
 
     /**
@@ -271,6 +543,6 @@ public void setLoopInterval(int loopInterval)
     public void stop()
     {
         internalStop();
-        setIsLooping(false);
+        setLooping(false);
     }
 }
diff --git a/src/org/jitsi/service/audionotifier/AudioNotifierService.java b/src/org/jitsi/service/audionotifier/AudioNotifierService.java
index 968ba73c15821e379694e9617f5fd3e84bb762e4..a6b09d1189e128012a46afc8286a9648a81e0a01 100644
--- a/src/org/jitsi/service/audionotifier/AudioNotifierService.java
+++ b/src/org/jitsi/service/audionotifier/AudioNotifierService.java
@@ -17,6 +17,13 @@
  */
 public interface AudioNotifierService
 {
+    /**
+     * Checks whether the playback and notification configuration
+     * share the same device.
+     * @return are audio out and notifications using the same device.
+     */
+    public boolean audioOutAndNotificationsShareSameDevice();
+
     /**
      * Creates an SCAudioClip and returns it. By default using notification
      * device.
@@ -34,11 +41,11 @@ public interface AudioNotifierService
     public SCAudioClip createAudio(String uri, boolean playback);
 
     /**
-     * Destroys the given audio.
+     * Specifies if currently the sound is off.
      *
-     * @param audio <tt>SCAudioClip</tt> to destroy
+     * @return TRUE if currently the sound is off, FALSE otherwise
      */
-    public void destroyAudio(SCAudioClip audio);
+    public boolean isMute();
 
     /**
      * Stops/Restores all currently playing sounds.
@@ -46,18 +53,4 @@ public interface AudioNotifierService
      * @param isMute mute or not currently playing sounds
      */
     public void setMute(boolean isMute);
-
-    /**
-     * Specifies if currently the sound is off.
-     *
-     * @return TRUE if currently the sound is off, FALSE otherwise
-     */
-    public boolean isMute();
-
-    /**
-     * Checks whether the playback and notification configuration
-     * share the same device.
-     * @return are audio out and notifications using the same device.
-     */
-    public boolean audioOutAndNotificationsShareSameDevice();
 }
diff --git a/src/org/jitsi/service/audionotifier/SCAudioClip.java b/src/org/jitsi/service/audionotifier/SCAudioClip.java
index 9699c0765efea1c3ff8d7462582dabc010d56417..5c6623dfe561e66f8c44644d4c2eb8c270d65330 100644
--- a/src/org/jitsi/service/audionotifier/SCAudioClip.java
+++ b/src/org/jitsi/service/audionotifier/SCAudioClip.java
@@ -9,8 +9,8 @@
 import java.util.concurrent.*;
 
 /**
- * SCAudioClip represents an audio clip created using the AudioNotifierService.
- * Like  any audio it could be played, stopped or played in loop.
+ * Represents an audio clip which could be played (optionally, in a loop) and
+ * stopped..
  *
  * @author Yana Stamcheva
  * @author Lyubomir Marinov
diff --git a/src/org/jitsi/util/swing/FitLayout.java b/src/org/jitsi/util/swing/FitLayout.java
index 783ba57cb683900ba581ac67bcbceb6cfb28ac71..fb7615d8f3c85be00fcfdf3aaa735adeb161bfad 100644
--- a/src/org/jitsi/util/swing/FitLayout.java
+++ b/src/org/jitsi/util/swing/FitLayout.java
@@ -11,14 +11,13 @@
 import javax.swing.*;
 
 /**
- * Represents a <code>LayoutManager</code> which centers the first
- * <code>Component</code> within its <code>Container</code> and, if the
- * preferred size of the <code>Component</code> is larger than the size of the
- * <code>Container</code>, scales the former within the bounds of the latter
- * while preserving the aspect ratio. <code>FitLayout</code> is appropriate for
- * <code>Container</code>s which display a single image or video
- * <code>Component</code> in its entirety for which preserving the aspect ratio
- * is important.
+ * Represents a <tt>LayoutManager</tt> which centers the first
+ * <tt>Component</tt> within its <tt>Container</tt> and, if the preferred size
+ * of the <tt>Component</tt> is larger than the size of the <tt>Container</tt>,
+ * scales the former within the bounds of the latter while preserving the aspect
+ * ratio. <tt>FitLayout</tt> is appropriate for <tt>Container</tt>s which
+ * display a single image or video <tt>Component</tt> in its entirety for which
+ * preserving the aspect ratio is important.
  * 
  * @author Lyubomir Marinov
  */
@@ -35,14 +34,13 @@ public void addLayoutComponent(String name, Component comp)
     }
 
     /**
-     * Gets the first <code>Component</code> of a specific
-     * <code>Container</code> if there is such a <code>Component</code>.
-     * 
-     * @param parent the <code>Container</code> to retrieve the first
-     *            <code>Component</code> of
-     * @return the first <code>Component</code> of a specific
-     *         <code>Container</code> if there is such a <code>Component</code>;
-     *         otherwise, <tt>null</tt>
+     * Gets the first <tt>Component</tt> of a specific <tt>Container</tt> if
+     * there is such a <tt>Component</tt>.
+     *
+     * @param parent the <tt>Container</tt> to retrieve the first
+     * <tt>Component</tt> of
+     * @return the first <tt>Component</tt> of a specific <tt>Container</tt> if
+     * there is such a <tt>Component</tt>; otherwise, <tt>null</tt>
      */
     protected Component getComponent(Container parent)
     {
@@ -172,10 +170,13 @@ public Dimension minimumLayoutSize(Container parent)
                 : new Dimension(0, 0);
     }
 
-    /*
-     * Since this LayoutManager lays out only the first Component of the
-     * specified parent Container, the preferred size of the Container is the
-     * preferred size of the mentioned Component.
+    /**
+     * {@inheritDoc}
+     *
+     * Since this <tt>LayoutManager</tt> lays out only the first
+     * <tt>Component</tt> of the specified parent <tt>Container</tt>, the
+     * preferred size of the <tt>Container</tt> is the preferred size of the
+     * mentioned <tt>Component</tt>.
      */
     public Dimension preferredLayoutSize(Container parent)
     {
@@ -187,9 +188,12 @@ public Dimension preferredLayoutSize(Container parent)
                 : new Dimension(0, 0);
     }
 
-    /*
-     * Does nothing because this LayoutManager lays out only the first Component
-     * of the parent Container and thus doesn't need String associations.
+    /**
+     * {@inheritDoc}
+     *
+     * Does nothing because this <tt>LayoutManager</tt> lays out only the first
+     * <tt>Component</tt> of the parent <tt>Container</tt> and thus doesn't need
+     * any <tt>String</tt> associations.
      */
     public void removeLayoutComponent(Component comp)
     {
diff --git a/src/org/jitsi/util/swing/VideoLayout.java b/src/org/jitsi/util/swing/VideoLayout.java
index 945657cef582762f9379626993a8e6e2f63abb92..37c96bcc71f7cff9d49421bd4dc73d374e0be13f 100644
--- a/src/org/jitsi/util/swing/VideoLayout.java
+++ b/src/org/jitsi/util/swing/VideoLayout.java
@@ -374,11 +374,9 @@ else if (remoteCount > 0)
     }
 
     /**
-     * Returns the minimum layout size for the given container.
+     * {@inheritDoc}
      *
-     * @param parent the container which minimum layout size we're looking for
-     * @return a Dimension containing, the minimum layout size for the given
-     * container
+     * The <tt>VideoLayout</tt> implementation of the method does nothing.
      */
     @Override
     public Dimension minimumLayoutSize(Container parent)