From cb0ee9c842539eca029aa22d9b60315eab6d74f0 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Sat, 10 Dec 2022 11:04:48 +0100 Subject: [PATCH] Dokumentation --- src/main/java/schule/ngb/zm/media/Audio.java | 45 ++++-- .../schule/ngb/zm/media/AudioListener.java | 8 +- src/main/java/schule/ngb/zm/media/Music.java | 142 ++++++++++-------- src/main/java/schule/ngb/zm/media/Sound.java | 112 +++++++------- 4 files changed, 173 insertions(+), 134 deletions(-) diff --git a/src/main/java/schule/ngb/zm/media/Audio.java b/src/main/java/schule/ngb/zm/media/Audio.java index 2b8301a..6a70735 100644 --- a/src/main/java/schule/ngb/zm/media/Audio.java +++ b/src/main/java/schule/ngb/zm/media/Audio.java @@ -1,10 +1,21 @@ package schule.ngb.zm.media; /** - * Interface für Audio-Medien. + * Schnittstelle für Audio-Medien. + * + *

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. */ public interface Audio { + /** + * @return Die Quelle, aus der das Medium geladen wurde. + */ String getSource(); /** @@ -17,7 +28,7 @@ public interface Audio { /** * Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn - * {@code isLooping() == true}, dann muss auch immer + * {@code isLooping() == true} gilt, dann muss auch immer * {@code isPlaying() == true} gelten. * * @return {@code true}, wenn das Medium in einer Schleife abgespielt wird, @@ -30,7 +41,7 @@ public interface Audio { *

* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton * und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des - * Mediums. + * Mediums. Negative Werte setzen die Lautstärke aud 0. * * @param volume Die neue Lautstärke zwischen 0 und 1. * @see * 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

- * 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; }