* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
@@ -50,8 +61,16 @@ public interface Audio {
double getVolume();
/**
- * Startet die Wiedergabe des Mediums und beendet die Methode. Das
- * Audio-Medium wird einmal abgespielt und stoppt dann.
+ * Startet die Wiedergabe des Mediums. Das Audio-Medium wird einmal
+ * abgespielt und stoppt dann.
+ *
+ * Die Methode beendet sofort und die Wiedergabe erfolgt im Hintergrund.
+ * Soll die Programmausführung erst nach Wiedergabe des Mediums fortgesetzt
+ * werden, sollte {@link #playAndWait()} verwendet werden.
+ *
+ * Soll die Wiedergabe im Hintergrund ablaufen, aber dennoch auf das Ende
+ * reagiert werden, kann ein
+ * {@link #addAudioListener(AudioListener) AudioListener} verwendet werden.
*/
void play();
@@ -63,28 +82,28 @@ public interface Audio {
/**
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
- * startet die Wiedergabe und beendet dann direkt die Methode. Um die
- * Wiedergabe zu stoppen muss {@link #stop()} aufgerufen werden.
+ * startet die Wiedergabe im Hintergrund und beendet dann sofort. Um die
+ * Wiedergabe zu stoppen, muss {@link #stop()} aufgerufen werden.
*/
void loop();
/**
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
- * {@code isPlaying() == false}, dann passiert nichts.
+ * ({@code isPlaying() == false}), dann passiert nichts.
*/
void stop();
/**
- * Stoppt die Wiedergabe und gibt alle Resourcen, die für das Medium
+ * Stoppt die Wiedergabe und gibt alle Ressourcen, die für das Medium
* verwendet werden, frei.
*/
void dispose();
/**
- * Fügt dem Medium das angegbene Objekt als {@code AudioListener} hinzu, der
- * bei Start und Stopp der Wiedergabe informiert werden soll.
+ * Fügt dem Medium das angegebene Objekt als {@code AudioListener} hinzu,
+ * der bei Start und Stopp der Wiedergabe informiert wird.
*
- * @param listener Das zu informierende Objekt.
+ * @param listener Das Listener-Objekt.
*/
void addAudioListener( 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 fd0b02b..41ba130 100644
--- a/src/main/java/schule/ngb/zm/media/AudioListener.java
+++ b/src/main/java/schule/ngb/zm/media/AudioListener.java
@@ -14,20 +14,20 @@ import schule.ngb.zm.util.events.Listener;
public interface AudioListener extends Listener {
/**
- * Wird aufgerufen, sobald die Wiedergabe eines Audio-Objektes startet, dem
+ * Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums startet, dem
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
* hinzugefügt wurde.
*
- * @param source Das Audio-Objekt, dessen Wiedergabe gestartet wurde.
+ * @param source Das Audio-Medium, dessen Wiedergabe gestartet wurde.
*/
void playbackStarted( Audio source );
/**
- * Wird aufgerufen, sobald die Wiedergabe eines Audio-Objektes stoppt, dem
+ * Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums stoppt, dem
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
* hinzugefügt wurde.
*
- * @param source Das Audio-Objekt, dessen Wiedergabe gestoppt wurde.
+ * @param source Das Audio-Medium, dessen Wiedergabe gestoppt wurde.
*/
void playbackStopped( Audio source );
diff --git a/src/main/java/schule/ngb/zm/media/Music.java b/src/main/java/schule/ngb/zm/media/Music.java
index 90e74c5..48005df 100644
--- a/src/main/java/schule/ngb/zm/media/Music.java
+++ b/src/main/java/schule/ngb/zm/media/Music.java
@@ -1,84 +1,103 @@
package schule.ngb.zm.media;
-import schule.ngb.zm.util.events.EventDispatcher;
-import schule.ngb.zm.util.tasks.TaskRunner;
import schule.ngb.zm.util.Log;
-import schule.ngb.zm.util.io.ResourceStreamProvider;
import schule.ngb.zm.util.Validator;
+import schule.ngb.zm.util.events.EventDispatcher;
+import schule.ngb.zm.util.io.ResourceStreamProvider;
+import schule.ngb.zm.util.tasks.TaskRunner;
import javax.sound.sampled.*;
import java.io.IOException;
import java.net.URL;
/**
- * Ein Musikstück, dass im Projekt abgespielt werden soll.
+ * Eine Musik, die abgespielt werden kann.
*
- * Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien,
- * die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher
- * nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle
- * gestreamt und wiedergegeben.
+ * Im Gegensatz zu einem {@link Sound} wird {@code Music} für längere
+ * Audiodateien benutzt, die zum Beispiel als Hintergrundmusik gespielt werden.
+ * Die Audiodaten werden daher nicht vollständig in den Speicher geladen,
+ * sondern direkt aus der Quelle gestreamt und direkt wiedergegeben.
+ *
+ * Daher ist es nicht möglich, die länge der Musik im Vorfeld abzufragen oder zu
+ * einer bestimmten Stelle im Stream zu springen.
+ *
+ *
MP3-Dateien verwenden
+ * Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
+ * MP3-Dateien zu nutzen, müssen die Bibliotheken jlayer , tritonus-share und mp3spi eingebunden werden.
+ * Details zur Verwendung können in der Dokumentation
+ * der Zeichenmaschine nachgelesen werden.
*/
-@SuppressWarnings("unused")
+// TODO: Wann sollten Listener beim Loopen informiert werden? Nach jedem Loop oder erst ganz am Ende?
+@SuppressWarnings( "unused" )
public class Music implements Audio {
- // size of the byte buffer used to read/write the audio stream
+ /**
+ * Größe des verwendeten Input-Puffers für die Audiodaten.
+ */
private static final int BUFFER_SIZE = 4096;
/**
- * Ob der Sound gerade abgespielt wird.
+ * Ob der Sound aktuell abgespielt wird.
*/
private boolean playing = false;
/**
- * Ob der Sound gerade in einer Schleife abgespielt wird.
+ * Ob der Sound aktuell in einer Schleife abgespielt wird.
*/
private boolean looping = false;
/**
- * Die Quelle des Musikstücks.
+ * Die Quelle der Audiodaten.
*/
private String audioSource;
/**
- * Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet
- * wurde. Sonst {@code null}.
+ * Der {@link AudioInputStream}, um die Audiosdaten zu lesen. {@code null},
+ * falls noch kein Stream geöffnet wurde.
*/
private AudioInputStream audioStream;
/**
- * Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst
- * {@code null}.
+ * Die {@link SourceDataLine} für die Ausgabe. {@code null}, falls die
+ * Audiodatei noch nicht geöffnet wurde.
*/
private SourceDataLine audioLine;
/**
- * Die Lautstärke der Musik.
+ * Die aktuelle Lautstärke des Mediums.
*/
private float volume = 0.8f;
+ /**
+ * Dispatcher für Audio-Events (start und stop).
+ */
EventDispatcher eventDispatcher;
- public Music( String source ) {
- Validator.requireNotNull(source);
- this.audioSource = source;
+ /**
+ * Erstellt eine Musik aus der angegebenen Audioquelle.
+ *
+ * @param audioSource Quelle der Audiodaten.
+ * @throws NullPointerException Falls die Quelle {@code null} ist.
+ * @see ResourceStreamProvider#getResourceURL(String)
+ */
+ public Music( String audioSource ) {
+ Validator.requireNotNull(audioSource);
+ this.audioSource = audioSource;
}
+ @Override
public String getSource() {
return audioSource;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean isPlaying() {
return playing;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean isLooping() {
if( !playing ) {
@@ -87,28 +106,21 @@ public class Music implements Audio {
return looping;
}
- /**
- * {@inheritDoc}
- */
@Override
public void setVolume( double volume ) {
- this.volume = (float) volume;
+ this.volume = volume < 0 ? 0f : (float) volume;
if( audioLine != null ) {
applyVolume();
}
}
- /**
- * {@inheritDoc}
- */
@Override
public double getVolume() {
return volume;
}
/**
- * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
- * anzuwenden.
+ * Wendet die Lautstärke vor dem Abspielen auf den Audiostream an.
*/
private void applyVolume() {
FloatControl gainControl =
@@ -119,24 +131,13 @@ public class Music implements Audio {
gainControl.setValue(vol);
}
- /**
- * {@inheritDoc}
- */
@Override
public void play() {
if( openLine() ) {
- TaskRunner.run(new Runnable() {
- @Override
- public void run() {
- stream();
- }
- });
+ TaskRunner.run(this::stream);
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void playAndWait() {
if( openLine() ) {
@@ -144,18 +145,12 @@ public class Music implements Audio {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void loop() {
looping = true;
play();
}
- /**
- * {@inheritDoc}
- */
@Override
public void stop() {
playing = false;
@@ -163,9 +158,6 @@ public class Music implements Audio {
dispose();
}
- /**
- * {@inheritDoc}
- */
@Override
public synchronized void dispose() {
if( audioLine != null ) {
@@ -189,7 +181,17 @@ public class Music implements Audio {
audioStream = null;
}
+ /**
+ * Startet den Stream der Audiodaten und damit die Wiedergabe.
+ *
+ * Die {@link #audioLine} muss vorher mit {@link #openLine()} geöffnet
+ * werden, ansonsten passiert nichts.
+ */
private synchronized void stream() {
+ if( audioLine == null ) {
+ return;
+ }
+
audioLine.start();
playing = true;
if( eventDispatcher != null ) {
@@ -226,6 +228,14 @@ public class Music implements Audio {
}
}
+ /**
+ * Öffnet eine {@link SourceDataLine} für die
+ * {@link #audioSource Audioquelle} und bereitet die Wiedergabe vor. Es wird
+ * noch nichts abgespielt.
+ *
+ * @return {@code true}, wenn die Line geöffnet werden konnte, {@code false}
+ * sonst.
+ */
private boolean openLine() {
if( audioLine != null ) {
return true;
@@ -262,6 +272,15 @@ public class Music implements Audio {
return false;
}
+ /**
+ * Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
+ * Aufruf von {@link #stop()} oder weil keine Audiodaten mehr vorhanden
+ * sind.
+ *
+ * Nach dem Ende des Streams wird {@link #dispose()} aufgerufen und, falls
+ * das Musikstück in einer Schleife abgespielt wird, der Stream direkt
+ * wieder gestartet.
+ */
private void streamingStopped() {
dispose();
@@ -289,14 +308,15 @@ public class Music implements Audio {
/**
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
- * aufgerufen, soblad sich auch ein Listener registrieren möchte.
- * @return
+ * aufgerufen, sobald sich der erste Listener anmelden möchte.
+ *
+ * @return Der {@code EventDispatcher} für dieses Objekt.
*/
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));
+ 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 224a118..cdb894c 100644
--- a/src/main/java/schule/ngb/zm/media/Sound.java
+++ b/src/main/java/schule/ngb/zm/media/Sound.java
@@ -10,80 +10,87 @@ import java.io.IOException;
import java.net.URL;
/**
- * Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
+ * Ein kurzer Soundclip, der mehrmals wiederverwendet werden kann.
*
- * In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds,
- * die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn
- * die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden
- * komplett in den Speicher geladen und können dadurch immer wieder, als
- * Schleife oder auch nur Abschnittsweise abgespielt werden.
+ * In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer
+ * Soundclips, die zusammen mit bestimmten Aktionen wiedergegeben werden (zum
+ * Beispiel, wenn die Spielfigur springt, wenn zwei Objekte kollidieren, usw.).
+ * Sounds werden vollständig in den Speicher geladen und können immer wieder,
+ * als Schleife oder auch nur Abschnittsweise, abgespielt werden.
*
- * Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die
- * KLasse {@link Music} an.
+ * Für längere Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher
+ * die Klasse {@link Music} an.
+ *
+ *
MP3-Dateien verwenden
+ * Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
+ * MP3-Dateien zu nutzen, müssen die Bibliotheken jlayer , tritonus-share und mp3spi eingebunden werden.
+ * Details zur Verwendung können in der Dokumentation
+ * der Zeichenmaschine nachgelesen werden.
*/
@SuppressWarnings( "unused" )
public class Sound implements Audio {
/**
- * Ob der Sound gerade abgespielt wird.
+ * Ob der Sound aktuell abgespielt wird.
*/
private boolean playing = false;
/**
- * Ob der Sound gerade in einer Schleife abgespielt wird.
+ * Ob der Sound aktuell in einer Schleife abgespielt wird.
*/
private boolean looping = false;
/**
- * Die Quelle des Musikstücks.
+ * Die Quelle der Audiodaten.
*/
private String audioSource;
/**
- * Der Clip, falls er schon geladen wurde, sonst {@code null}.
+ * Der Clip, falls er schon geladen wurde. Ansonsten {@code null}.
*/
private Clip audioClip;
/**
- * Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen
+ * Ob die Ressourcen des Clips im Speicher nach dem nächsten Abspielen
* freigegeben werden sollen.
*/
private boolean disposeAfterPlay = false;
/**
- * Die Lautstärke des Clips.
+ * Die aktuelle Lautstärke des Clips.
*/
private float volume = 0.8f;
+ /**
+ * Dispatcher für Audio-Events (start und stop).
+ */
EventDispatcher eventDispatcher;
/**
* Erstellt einen Sound aus der angegebene Quelle.
*
- * @param source Ein Dateipfad oder eine Webadresse.
+ * @param source Quelle der Audiodaten.
* @throws NullPointerException Falls die Quelle {@code null} ist.
+ * @see ResourceStreamProvider#getResourceURL(String)
*/
public Sound( String source ) {
Validator.requireNotNull(source);
this.audioSource = source;
}
+ @Override
public String getSource() {
return audioSource;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean isPlaying() {
// return audioClip != null && audioClip.isRunning();
return playing;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean isLooping() {
if( !playing ) {
@@ -92,28 +99,21 @@ public class Sound implements Audio {
return looping;
}
- /**
- * {@inheritDoc}
- */
@Override
public void setVolume( double volume ) {
- this.volume = (float) volume;
+ this.volume = volume < 0 ? 0f : (float) volume;
if( audioClip != null ) {
applyVolume();
}
}
- /**
- * {@inheritDoc}
- */
@Override
public double getVolume() {
return volume;
}
/**
- * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
- * anzuwenden.
+ * Wendet die Lautstärke vor dem Abspielen auf den Clip an.
*/
private void applyVolume() {
FloatControl gainControl =
@@ -124,9 +124,6 @@ public class Sound implements Audio {
gainControl.setValue(vol);
}
- /**
- * {@inheritDoc}
- */
@Override
public void stop() {
looping = false;
@@ -136,9 +133,6 @@ public class Sound implements Audio {
playing = false;
}
- /**
- * {@inheritDoc}
- */
@Override
public void play() {
if( this.openClip() ) {
@@ -147,9 +141,6 @@ public class Sound implements Audio {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void playAndWait() {
this.play();
@@ -168,7 +159,7 @@ public class Sound implements Audio {
}
/**
- * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
+ * Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
* frei.
*
* Der Aufruf ist effektiv gleich zu
@@ -185,7 +176,7 @@ public class Sound implements Audio {
}
/**
- * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
+ * Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
* frei.
*
* Der Aufruf entspricht
@@ -199,17 +190,18 @@ public class Sound implements Audio {
playAndWait();
}
- /**
- * {@inheritDoc}
- */
@Override
public void loop() {
loop(Clip.LOOP_CONTINUOUSLY);
}
/**
- * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und
- * stoppt die Wiedergabe dann.
+ * Wiederholt den Sound die angegebene Anzahl an Wiederholungen und stoppt
+ * die Wiedergabe dann.
+ *
+ * Wird {@code count} auf {@link Clip#LOOP_CONTINUOUSLY} gesetzt (-1), wird
+ * der Clip unendlich oft wiederholt. Der Aufruf entspricht dann
+ * {@link #loop()}.
*
* @param count Anzahl der Wiederholungen.
*/
@@ -231,9 +223,6 @@ public class Sound implements Audio {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public synchronized void dispose() {
if( audioClip != null ) {
@@ -245,6 +234,13 @@ public class Sound implements Audio {
}
}
+ /**
+ * Lädt falls nötig den {@link Clip} für die
+ * {@link #audioSource Audioquelle} und startet die Wiedergabe.
+ *
+ * @return {@code true}, wenn der Clip geöffnet werden konnte, {@code false}
+ * sonst.
+ */
private synchronized boolean openClip() {
if( audioClip != null ) {
audioClip.setFramePosition(0);
@@ -307,9 +303,12 @@ public class Sound implements Audio {
}*/
/**
- * Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird.
- * Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe
- * nach {@link #playOnce()} beendet wurde.
+ * Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
+ * Aufruf von {@link #stop()} oder, weil die Wiedergabe nach
+ * {@link #playOnce()} beendet wurde.
+ *
+ * Falls {@link #disposeAfterPlay} gesetzt ist, wird nach dem Ende der
+ * Wiedergabe {@link #dispose()} aufgerufen.
*/
private void playbackStopped() {
playing = false;
@@ -332,14 +331,15 @@ public class Sound implements Audio {
/**
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
- * aufgerufen, soblad sich auch ein Listener registrieren möchte.
- * @return
+ * aufgerufen, sobald sich der erste Listener anmelden möchte.
+ *
+ * @return Der {@code EventDispatcher} für dieses Objekt.
*/
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));
+ eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
+ eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
}
return eventDispatcher;
}