diff --git a/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java b/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java index 7fc5a3595899b38732e15d4f7c39afab0efd564d..2867bb40ca8a19f1814d794d65db513a17295b78 100644 --- a/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java +++ b/src/org/jitsi/impl/neomedia/notify/AudioNotifierServiceImpl.java @@ -10,14 +10,14 @@ import java.net.*; import java.util.*; +import javax.media.*; + import org.jitsi.impl.neomedia.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.service.audionotifier.*; import org.jitsi.service.libjitsi.*; import org.jitsi.service.resources.*; -import javax.media.*; - /** * The implementation of the AudioNotifierService. * @@ -30,8 +30,8 @@ public class AudioNotifierServiceImpl /** * Map of different audio clips. */ - private static final Map<AudioClipsKey, SCAudioClipImpl> audioClips = - new HashMap<AudioClipsKey, SCAudioClipImpl>(); + private static final Map<AudioClipsKey, AbstractSCAudioClip> audioClips + = new HashMap<AudioClipsKey, AbstractSCAudioClip>(); /** * If the sound is currently disabled. @@ -63,7 +63,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 SCAudioClipImpl createAudio(String uri) + public AbstractSCAudioClip createAudio(String uri) { return createAudio(uri, false); } @@ -76,9 +76,9 @@ public SCAudioClipImpl createAudio(String uri) * @param playback use or not the playback device. * @return a newly created <tt>SCAudioClip</tt> from <tt>uri</tt> */ - public SCAudioClipImpl createAudio(String uri, boolean playback) + public AbstractSCAudioClip createAudio(String uri, boolean playback) { - SCAudioClipImpl audioClip; + AbstractSCAudioClip audioClip; synchronized (audioClips) { @@ -122,8 +122,11 @@ else if (NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase( audioClip = null; else { - audioClip = new AudioSystemClipImpl( - url, this, audioSystem, playback); + audioClip + = new AudioSystemClipImpl( + url, + this, audioSystem, + playback); } } catch (Throwable t) @@ -151,7 +154,7 @@ public void destroyAudio(SCAudioClip audioClip) synchronized (audioClips) { AudioClipsKey keyToRemove = null; - for(Map.Entry<AudioClipsKey, SCAudioClipImpl> entry + for(Map.Entry<AudioClipsKey, AbstractSCAudioClip> entry : audioClips.entrySet()) { if(entry.getValue().equals(audioClip)) @@ -175,7 +178,7 @@ public void setMute(boolean isMute) { this.isMute = isMute; - for (SCAudioClipImpl audioClip : audioClips.values()) + for (AbstractSCAudioClip audioClip : audioClips.values()) { if (isMute) { @@ -183,7 +186,7 @@ public void setMute(boolean isMute) } else if (audioClip.isLooping()) { - audioClip.playInLoop(audioClip.getLoopInterval()); + // TODO Auto-generated method stub } } } diff --git a/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java b/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java index d62c6dcc9d38342790212348eaeac18c1caa9260..c5be2d527d87495395a1b546e888a9bba5d58493 100644 --- a/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java +++ b/src/org/jitsi/impl/neomedia/notify/AudioSystemClipImpl.java @@ -13,16 +13,17 @@ import javax.sound.sampled.*; import org.jitsi.impl.neomedia.codec.audio.speex.*; +import org.jitsi.service.audionotifier.*; import org.jitsi.util.*; /** * Implementation of SCAudioClip using PortAudio. * * @author Damyian Minkov - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class AudioSystemClipImpl - extends SCAudioClipImpl + extends AbstractSCAudioClip { /** * The <tt>Logger</tt> used by the <tt>AudioSystemClipImpl</tt> class and @@ -31,18 +32,16 @@ public class AudioSystemClipImpl private static final Logger logger = Logger.getLogger(AudioSystemClipImpl.class); - private final AudioNotifierServiceImpl audioNotifier; - private final org.jitsi.impl.neomedia.device.AudioSystem audioSystem; - private boolean started = false; - - private final Object syncObject = new Object(); + private Buffer buffer; + + private byte[] bufferData; - private final URL url; + private final boolean playback; - private boolean isPlayback = false; + private Renderer renderer; /** * Creates the audio clip and initializes the listener used from the @@ -55,154 +54,58 @@ public class AudioSystemClipImpl */ public AudioSystemClipImpl( URL url, - AudioNotifierServiceImpl audioNotifier, + AudioNotifierService audioNotifier, org.jitsi.impl.neomedia.device.AudioSystem audioSystem, boolean playback) throws IOException { - this.url = url; - this.audioNotifier = audioNotifier; - this.audioSystem = audioSystem; - this.isPlayback = playback; - } + super(url, audioNotifier); - /** - * Plays this audio. - */ - public void play() - { - if ((url != null) && !audioNotifier.isMute()) - { - started = true; - new Thread() - { - @Override - public void run() - { - runInPlayThread(); - } - }.start(); - } + this.audioSystem = audioSystem; + this.playback = playback; } /** - * Plays this audio in loop. - * - * @param interval the loop interval + * {@inheritDoc} */ - public void playInLoop(int interval) + @Override + protected void enterRunInPlayThread() { - setLoopInterval(interval); - setIsLooping(true); + buffer = new Buffer(); + bufferData = new byte[1024]; + buffer.setData(bufferData); - play(); + renderer = audioSystem.createRenderer(playback); } /** - * Stops this audio. + * {@inheritDoc} */ - public void stop() + @Override + protected void exitRunInPlayThread() { - internalStop(); - setIsLooping(false); + buffer = null; + bufferData = null; + renderer = null; } /** - * 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). + * {@inheritDoc} */ - public void internalStop() + @Override + protected void exitRunOnceInPlayThread() { - synchronized (syncObject) + try { - if (url != null && started) - { - started = false; - syncObject.notifyAll(); - } + renderer.stop(); } - } - - /** - * Runs in a separate thread to perform the actual playback of the audio - * stream pointed to by {@link #url} looping as necessary. - */ - private void runInPlayThread() - { - Buffer buffer = new Buffer(); - byte[] bufferData = new byte[1024]; - // don't enable volume control for notifications - - Renderer renderer = audioSystem.createRenderer(isPlayback); - - buffer.setData(bufferData); - while (started) + finally { - try - { - if (!runOnceInPlayThread(renderer, buffer, bufferData)) - break; - } - finally - { - try - { - renderer.stop(); - } - finally - { - renderer.close(); - } - } - - if(isLooping()) - { - synchronized(syncObject) - { - if (started) - { - try - { - // only wait if lonnger than 0 - // or we will wait forever - if(getLoopInterval() > 0) - syncObject.wait(getLoopInterval()); - } - catch (InterruptedException e) - { - } - } - } - } - else - break; + renderer.close(); } } - /** - * Runs in a separate thread to perform the actual playback of the audio - * stream pointed to by {@link #url} once using a specific - * <tt>PortAudioRenderer</tt> and giving it the audio data for processing - * through a specific JMF <tt>Buffer</tt>. - * - * @param renderer the <tt>PortAudioRenderer</tt> which is to render the - * audio data read from the audio stream pointed to by {@link #url} - * @param buffer the JMF <tt>Buffer</tt> through which the audio data to be - * rendered is to be given to <tt>renderer</tt> - * @param bufferData the value of the <tt>data</tt> property of - * <tt>buffer</tt> explicitly specified for performance reasons so that it - * doesn't have to be read and cast during every iteration of the playback - * loop - * @return <tt>true</tt> if the playback was successful and it is to be - * carried out again in accord with the <tt>looping</tt> property value of - * this <tt>SCAudioClipImpl</tt>; otherwise, <tt>false</tt> - */ - private boolean runOnceInPlayThread( - Renderer renderer, - Buffer buffer, - byte[] bufferData) + protected boolean runOnceInPlayThread() { AudioInputStream audioStream = null; @@ -288,7 +191,7 @@ private boolean runOnceInPlayThread( int bufferLength; - while(started + while(isStarted() && ((bufferLength = audioStream.read(bufferData)) != -1)) { diff --git a/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java b/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java index 26f705d215a9f6ffeddfa804dd20ce37479f807e..2d42a48b25b21a5fd5b89da9f0514258d7771714 100644 --- a/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java +++ b/src/org/jitsi/impl/neomedia/notify/JavaSoundClipImpl.java @@ -7,14 +7,11 @@ package org.jitsi.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 org.jitsi.service.audionotifier.*; /** @@ -23,98 +20,33 @@ * @author Yana Stamcheva */ public class JavaSoundClipImpl - extends SCAudioClipImpl - implements ActionListener + extends AbstractSCAudioClip { private static Constructor<AudioClip> 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 JavaSoundClipImpl(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) + @SuppressWarnings("unchecked") + private static Constructor<AudioClip> createAcConstructor() + throws ClassNotFoundException, + NoSuchMethodException, + SecurityException { - if(!audioNotifier.isMute()) + Class<?> class1; + try { - 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(); - } + class1 + = Class.forName( + "com.sun.media.sound.JavaSoundAudioClip", + true, + ClassLoader.getSystemClassLoader()); } - - setLoopInterval(interval); - setIsLooping(true); - } - - /** - * Stops this audio. - */ - public void stop() - { - if (audioClip != null) - audioClip.stop(); - - if (isLooping()) + catch (ClassNotFoundException cnfex) { - playAudioTimer.stop(); - setIsLooping(false); + class1 + = Class.forName("sun.audio.SunAudioClip", true, null); } - } - - /** - * 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(); + return + (Constructor<AudioClip>) class1.getConstructor(InputStream.class); } /** @@ -162,40 +94,52 @@ public Constructor<AudioClip> run() } } - @SuppressWarnings("unchecked") - private static Constructor<AudioClip> createAcConstructor() - throws ClassNotFoundException, - NoSuchMethodException, - SecurityException + private final AudioClip audioClip; + + /** + * 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 JavaSoundClipImpl(URL url, AudioNotifierService audioNotifier) + throws IOException + { + super(url, audioNotifier); + + audioClip = createAppletAudioClip(url.openStream()); + } + + /** + * {@inheritDoc} + */ + @Override + public void internalStop() { - Class<?> class1; try { - class1 - = Class.forName( - "com.sun.media.sound.JavaSoundAudioClip", - true, - ClassLoader.getSystemClassLoader()); + if (audioClip != null) + audioClip.stop(); } - catch (ClassNotFoundException cnfex) + finally { - class1 - = Class.forName("sun.audio.SunAudioClip", true, null); + super.internalStop(); } - return - (Constructor<AudioClip>) class1.getConstructor(InputStream.class); } /** - * Plays an audio clip. Used in the playAudioTimer to play an audio in loop. - * @param e the event. + * {@inheritDoc} */ - public void actionPerformed(ActionEvent e) + protected boolean runOnceInPlayThread() { - if (audioClip != null) + if (audioClip == null) + return false; + else { - audioClip.stop(); audioClip.play(); + return true; } } } diff --git a/src/org/jitsi/impl/neomedia/notify/SCAudioClipImpl.java b/src/org/jitsi/impl/neomedia/notify/SCAudioClipImpl.java deleted file mode 100644 index 94657eb7ef6fb2cf3742b67b18faf60d0ab6d5e3..0000000000000000000000000000000000000000 --- a/src/org/jitsi/impl/neomedia/notify/SCAudioClipImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Jitsi, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package org.jitsi.impl.neomedia.notify; - -import org.jitsi.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(); -} diff --git a/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java b/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java new file mode 100644 index 0000000000000000000000000000000000000000..161dea63851802e0bf1df3102d99b18dcf84f55b --- /dev/null +++ b/src/org/jitsi/service/audionotifier/AbstractSCAudioClip.java @@ -0,0 +1,276 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.service.audionotifier; + +import java.net.*; +import java.util.concurrent.*; + +/** + * An abstract base implementation of {@link SCAudioClip}. + * + * @author Damian Minkov + * @author Lyubomir Marinov + */ +public abstract class AbstractSCAudioClip + implements SCAudioClip +{ + protected final AudioNotifierService audioNotifier; + + private boolean isInvalid; + + private boolean isLooping; + + private int loopInterval; + + private boolean started = false; + + private final Object sync = new Object(); + + protected final URL url; + + protected AbstractSCAudioClip( + URL url, + AudioNotifierService audioNotifier) + { + this.url = url; + this.audioNotifier = audioNotifier; + } + + protected void enterRunInPlayThread() + { + // TODO Auto-generated method stub + } + + protected void enterRunOnceInPlayThread() + { + // TODO Auto-generated method stub + } + + protected void exitRunInPlayThread() + { + // TODO Auto-generated method stub + } + + 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 + */ + public int getLoopInterval() + { + return loopInterval; + } + + /** + * 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() + { + synchronized (sync) + { + if (url != null && started) + { + started = false; + sync.notifyAll(); + } + } + } + + /** + * Returns TRUE if this audio is invalid, FALSE otherwise. + * + * @return TRUE if this audio is invalid, FALSE otherwise + */ + public boolean isInvalid() + { + return 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; + } + + protected boolean isStarted() + { + return started; + } + + /** + * {@inheritDoc} + */ + public void play() + { + play(-1, null); + } + + /** + * {@inheritDoc} + */ + 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()) + { + started = true; + new Thread() + { + @Override + public void run() + { + runInPlayThread(loopCondition); + } + }.start(); + } + } + + private void runInPlayThread(Callable<Boolean> loopCondition) + { + enterRunInPlayThread(); + try + { + while (started) + { + enterRunOnceInPlayThread(); + try + { + if (!runOnceInPlayThread()) + break; + } + finally + { + exitRunOnceInPlayThread(); + } + + if(isLooping()) + { + 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) + { + } + } + } + + if (started) + { + if (loopCondition == null) + { + /* + * The interface contract is that this audio plays + * once only if the loopCondition is null. + */ + break; + } + else + { + boolean loop = false; + + 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; + } + } + } + else + break; + } + else + break; + } + } + finally + { + exitRunInPlayThread(); + } + } + + protected abstract boolean runOnceInPlayThread(); + + /** + * 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); + } + + /** + * @param isInvalid the isInvalid to set + */ + public void setIsInvalid(boolean isInvalid) + { + this.isInvalid = isInvalid; + } + + /** + * @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; + } + + /** + * {@inheritDoc} + */ + public void stop() + { + internalStop(); + setIsLooping(false); + } +} diff --git a/src/org/jitsi/service/audionotifier/SCAudioClip.java b/src/org/jitsi/service/audionotifier/SCAudioClip.java index 146ac16cfaa3243bffc42ec9af46445459609706..9699c0765efea1c3ff8d7462582dabc010d56417 100644 --- a/src/org/jitsi/service/audionotifier/SCAudioClip.java +++ b/src/org/jitsi/service/audionotifier/SCAudioClip.java @@ -6,28 +6,40 @@ */ package org.jitsi.service.audionotifier; +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. * * @author Yana Stamcheva + * @author Lyubomir Marinov */ public interface SCAudioClip { /** - * Plays this audio. + * Starts playing this audio once only. The method behaves as if + * {@link #play(int, Callable)} was invoked with a negative + * <tt>loopInterval</tt> and/or <tt>null</tt> <tt>loopCondition</tt>. */ public void play(); /** - * Plays this audio in loop. + * Starts playing this audio. Optionally, the playback is looped. * - * @param silenceInterval interval between loops + * @param loopInterval the interval of time in milliseconds between + * consecutive plays of this audio. If negative, this audio is played once + * only and <tt>loopCondition</tt> is ignored. + * @param loopCondition a <tt>Callable</tt> which is called at the beginning + * of each iteration of looped playback of this audio except the first one + * to determine whether to continue the loop. If <tt>loopInterval</tt> is + * negative or <tt>loopCondition</tt> is <tt>null</tt>, this audio is played + * once only. */ - public void playInLoop(int silenceInterval); + public void play(int loopInterval, Callable<Boolean> loopCondition); /** - * Stops this audio. + * Stops playing this audio. */ public void stop(); }