diff --git a/src/main/java/schule/ngb/zm/media/Audio.java b/src/main/java/schule/ngb/zm/media/Audio.java
index 6e8640b..2b8301a 100644
--- a/src/main/java/schule/ngb/zm/media/Audio.java
+++ b/src/main/java/schule/ngb/zm/media/Audio.java
@@ -80,4 +80,19 @@ public interface Audio {
*/
void dispose();
+ /**
+ * Fügt dem Medium das angegbene Objekt als {@code AudioListener} hinzu, der
+ * bei Start und Stopp der Wiedergabe informiert werden soll.
+ *
+ * @param listener Das zu informierende Objekt.
+ */
+ void addAudioListener( AudioListener listener );
+
+ /**
+ * Entfernt den angegebenen {@code AudioListener} vom Medium.
+ *
+ * @param listener Das Listener-Objekt.
+ */
+ void removeAudioListener( AudioListener listener );
+
}
diff --git a/src/main/java/schule/ngb/zm/media/AudioListener.java b/src/main/java/schule/ngb/zm/media/AudioListener.java
index 1ffe45d..fd0b02b 100644
--- a/src/main/java/schule/ngb/zm/media/AudioListener.java
+++ b/src/main/java/schule/ngb/zm/media/AudioListener.java
@@ -2,10 +2,33 @@ package schule.ngb.zm.media;
import schule.ngb.zm.util.events.Listener;
+/**
+ * Interface für Klassen, die auf das starten und stoppen der Wiedergabe von
+ * {@link Audio}-Objekten reagieren möchten.
+ *
+ * Implementierende Klassen können sich bei einem Auido-Objekt mittels
+ * {@link Audio#addAudioListener(AudioListener)} anmelden und werden über die
+ * jeweilige Methode informiert, sobald die Wiedergabe gestartet oder gestoppt
+ * wird.
+ */
public interface AudioListener extends Listener {
- void start( Audio source );
+ /**
+ * Wird aufgerufen, sobald die Wiedergabe eines Audio-Objektes startet, dem
+ * dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
+ * hinzugefügt wurde.
+ *
+ * @param source Das Audio-Objekt, dessen Wiedergabe gestartet wurde.
+ */
+ void playbackStarted( Audio source );
- void stop( Audio source );
+ /**
+ * Wird aufgerufen, sobald die Wiedergabe eines Audio-Objektes stoppt, dem
+ * dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
+ * hinzugefügt wurde.
+ *
+ * @param source Das Audio-Objekt, dessen Wiedergabe gestoppt wurde.
+ */
+ void playbackStopped( Audio source );
}
diff --git a/src/main/java/schule/ngb/zm/media/Mixer.java b/src/main/java/schule/ngb/zm/media/Mixer.java
index 9e893ab..58ba2c4 100644
--- a/src/main/java/schule/ngb/zm/media/Mixer.java
+++ b/src/main/java/schule/ngb/zm/media/Mixer.java
@@ -1,9 +1,11 @@
package schule.ngb.zm.media;
import schule.ngb.zm.Constants;
+import schule.ngb.zm.util.events.EventDispatcher;
import schule.ngb.zm.util.tasks.TaskRunner;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
/**
@@ -18,24 +20,14 @@ import java.util.List;
* Darüber hinaus kann ein Mixer Effekte wie einen
* {@link #fade(double, int) fadeIn} auf die Medien anwenden.
*/
-public class Mixer implements Audio {
+@SuppressWarnings( "unused" )
+public class Mixer implements Audio, AudioListener {
private List audios;
private float volume = 0.8f;
- class AudioWrapper {
-
- Audio audio;
-
- float volumeFactor;
-
- public AudioWrapper( Audio audio, float volumeFactor ) {
- this.audio = audio;
- this.volumeFactor = volumeFactor;
- }
-
- }
+ EventDispatcher eventDispatcher;
public Mixer() {
this.audios = new ArrayList<>(4);
@@ -45,16 +37,61 @@ public class Mixer implements Audio {
return "";
}
- public void add( Audio pAudio ) {
- add(pAudio, 1f);
+ private AudioWrapper findWrapper( Audio pAudio ) {
+ for( AudioWrapper aw: audios ) {
+ if( aw.audio == pAudio ) {
+ return aw;
+ }
+ }
+ return null;
}
+ public boolean contains( Audio pAudio ) {
+ return findWrapper(pAudio) != null;
+ }
+
+ public void add( Audio pAudio ) {
+ add(pAudio, 1);
+ }
+
+ /**
+ * Fügt ein Audio-Objekt dem Mixer mit dem angegebenen Lautstärke-Faktor
+ * hinzu.
+ *
+ * Der Lautstärke-Faktor setzt die Lautstärke des Audio-Objektes relativ zur
+ * Lautstärke des Mixers. Bei einem Faktor von 1.0 wird die Lautstärke des
+ * Mixers übernommen. Bei einem Wert von 0.5 wird das Objekt halb so laut
+ * abgespielt. Auf diese Weise lässt sich die Lautstärke aller Audio-Objekte
+ * des Mixers gleichzeitig anpassen, während ihre relative Lautstärke
+ * zueinander gleich bleibt.
+ *
+ * @param pAudio Ein Audio-Objekt.
+ * @param pVolumeFactor Der Lautstärke-Faktor.
+ */
public void add( Audio pAudio, double pVolumeFactor ) {
- audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
+ if( !contains(pAudio) ) {
+ audios.add(new AudioWrapper(pAudio, (float) pVolumeFactor));
+ } else {
+ findWrapper(pAudio).volumeFactor = (float) pVolumeFactor;
+ }
pAudio.setVolume(pVolumeFactor * volume);
}
+ /**
+ * Entfernt die das angegebene Audio-Objekt aus dem Mixer. Ist das Objekt
+ * nicht Teil des Mixers, passiert nichts.
+ *
+ * @param pAudio Ein Audio-Objekt.
+ */
public void remove( Audio pAudio ) {
+ Iterator it = audios.listIterator();
+ while( it.hasNext() ) {
+ AudioWrapper aw = it.next();
+ if( aw.audio == pAudio ) {
+ it.remove();
+ break;
+ }
+ }
}
public void removeAll() {
@@ -178,4 +215,57 @@ public class Mixer implements Audio {
});
}
+ @Override
+ public void playbackStarted( Audio source ) {
+ if( eventDispatcher != null ) {
+ eventDispatcher.dispatchEvent("start", Mixer.this);
+ }
+ }
+
+ @Override
+ public void playbackStopped( Audio source ) {
+ if( !isPlaying() ) {
+ if( eventDispatcher != null ) {
+ eventDispatcher.dispatchEvent("stop", Mixer.this);
+ }
+ }
+ }
+
+ @Override
+ public void addAudioListener( AudioListener listener ) {
+ initializeEventDispatcher().addListener(listener);
+ }
+
+ @Override
+ public void removeAudioListener( AudioListener listener ) {
+ initializeEventDispatcher().removeListener(listener);
+ }
+
+ /**
+ * Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
+ * aufgerufen, soblad sich auch ein Listener registrieren möchte.
+ * @return
+ */
+ private EventDispatcher initializeEventDispatcher() {
+ if( eventDispatcher == null ) {
+ eventDispatcher = new EventDispatcher<>();
+ eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
+ eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
+ }
+ return eventDispatcher;
+ }
+
+ class AudioWrapper {
+
+ Audio audio;
+
+ float volumeFactor;
+
+ public AudioWrapper( Audio audio, float volumeFactor ) {
+ this.audio = audio;
+ this.volumeFactor = volumeFactor;
+ }
+
+ }
+
}
diff --git a/src/main/java/schule/ngb/zm/media/Music.java b/src/main/java/schule/ngb/zm/media/Music.java
index 8805514..90e74c5 100644
--- a/src/main/java/schule/ngb/zm/media/Music.java
+++ b/src/main/java/schule/ngb/zm/media/Music.java
@@ -18,6 +18,7 @@ import java.net.URL;
* nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle
* gestreamt und wiedergegeben.
*/
+@SuppressWarnings("unused")
public class Music implements Audio {
// size of the byte buffer used to read/write the audio stream
@@ -276,11 +277,13 @@ public class Music implements Audio {
}
}
- public void addListener( AudioListener listener ) {
+ @Override
+ public void addAudioListener( AudioListener listener ) {
initializeEventDispatcher().addListener(listener);
}
- public void removeListener( AudioListener listener ) {
+ @Override
+ public void removeAudioListener( AudioListener listener ) {
initializeEventDispatcher().removeListener(listener);
}
@@ -292,8 +295,8 @@ public class Music implements Audio {
private EventDispatcher initializeEventDispatcher() {
if( eventDispatcher == null ) {
eventDispatcher = new EventDispatcher<>();
- eventDispatcher.registerEventType("start", (a,l) -> l.start(a));
- eventDispatcher.registerEventType("stop", (a,l) -> l.stop(a));
+ eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
+ eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
}
return eventDispatcher;
}
diff --git a/src/main/java/schule/ngb/zm/media/Sound.java b/src/main/java/schule/ngb/zm/media/Sound.java
index ec366b4..224a118 100644
--- a/src/main/java/schule/ngb/zm/media/Sound.java
+++ b/src/main/java/schule/ngb/zm/media/Sound.java
@@ -2,6 +2,7 @@ package schule.ngb.zm.media;
import schule.ngb.zm.util.Log;
import schule.ngb.zm.util.Validator;
+import schule.ngb.zm.util.events.EventDispatcher;
import schule.ngb.zm.util.io.ResourceStreamProvider;
import javax.sound.sampled.*;
@@ -54,6 +55,8 @@ public class Sound implements Audio {
*/
private float volume = 0.8f;
+ EventDispatcher eventDispatcher;
+
/**
* Erstellt einen Sound aus der angegebene Quelle.
*
@@ -235,7 +238,7 @@ public class Sound implements Audio {
public synchronized void dispose() {
if( audioClip != null ) {
if( audioClip.isRunning() ) {
- audioClip.stop();
+ stop();
}
audioClip.close();
audioClip = null;
@@ -259,8 +262,16 @@ public class Sound implements Audio {
audioClip.addLineListener(new LineListener() {
@Override
public void update( LineEvent event ) {
- if( event.getType() == LineEvent.Type.STOP ) {
+ if( event.getType() == LineEvent.Type.START ) {
+ if( eventDispatcher != null ) {
+ eventDispatcher.dispatchEvent("start", Sound.this);
+ }
+ } else if( event.getType() == LineEvent.Type.STOP ) {
playbackStopped();
+
+ if( eventDispatcher != null ) {
+ eventDispatcher.dispatchEvent("stop", Sound.this);
+ }
}
}
});
@@ -302,12 +313,37 @@ public class Sound implements Audio {
*/
private void playbackStopped() {
playing = false;
+
if( disposeAfterPlay ) {
this.dispose();
disposeAfterPlay = false;
}
}
+ @Override
+ public void addAudioListener( AudioListener listener ) {
+ initializeEventDispatcher().addListener(listener);
+ }
+
+ @Override
+ public void removeAudioListener( AudioListener listener ) {
+ initializeEventDispatcher().removeListener(listener);
+ }
+
+ /**
+ * Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
+ * aufgerufen, soblad sich auch ein Listener registrieren möchte.
+ * @return
+ */
+ private EventDispatcher initializeEventDispatcher() {
+ if( eventDispatcher == null ) {
+ eventDispatcher = new EventDispatcher<>();
+ eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
+ eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
+ }
+ return eventDispatcher;
+ }
+
private static final Log LOG = Log.getLogger(Sound.class);
}