From ec8e5cea9161dfeb0dc2d5893583c05053d8c818 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Mon, 11 Jul 2022 13:53:15 +0200 Subject: [PATCH] API angepasst und Javadoc verbessert --- src/main/java/schule/ngb/zm/media/Audio.java | 19 +- src/main/java/schule/ngb/zm/media/Mixer.java | 55 +++++- src/main/java/schule/ngb/zm/media/Music.java | 104 +++++++++-- src/main/java/schule/ngb/zm/media/Sound.java | 185 +++++++++++++++---- 4 files changed, 294 insertions(+), 69 deletions(-) diff --git a/src/main/java/schule/ngb/zm/media/Audio.java b/src/main/java/schule/ngb/zm/media/Audio.java index 1922946..fa64162 100644 --- a/src/main/java/schule/ngb/zm/media/Audio.java +++ b/src/main/java/schule/ngb/zm/media/Audio.java @@ -18,8 +18,8 @@ public interface Audio { * {@code isLooping() == true}, dann muss auch immer * {@code isPlaying() == true} gelten. * - * @return {@code true}, wenn das Medium in einer Schleife - * abgespielt wird, {@code false} sonst. + * @return {@code true}, wenn das Medium in einer Schleife abgespielt wird, + * {@code false} sonst. */ boolean isLooping(); @@ -36,17 +36,28 @@ public interface Audio { */ void setVolume( double volume ); + /** + * Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück. + *

+ * 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 + * Mediums. + * + * @return Die Lautstärke als linear skalierter Wert. + */ + double getVolume(); + /** * Startet die Wiedergabe des Mediums und beendet die Methode. Das * Audio-Medium wird einmal abgespielt und stoppt dann. */ - void playOnce(); + void play(); /** * Startet die Wiedergabe des Mediums und blockiert das Programm, bis die * Wiedergabe beendet ist. */ - void playOnceAndWait(); + void playAndWait(); /** * Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode diff --git a/src/main/java/schule/ngb/zm/media/Mixer.java b/src/main/java/schule/ngb/zm/media/Mixer.java index 91b2064..f94ec4f 100644 --- a/src/main/java/schule/ngb/zm/media/Mixer.java +++ b/src/main/java/schule/ngb/zm/media/Mixer.java @@ -50,30 +50,53 @@ public class Mixer implements Audio { pAudio.setVolume(pVolumeFactor * volume); } + /** + * {@inheritDoc} + */ @Override public boolean isPlaying() { return audios.stream().anyMatch(aw -> aw.audio.isPlaying()); } + /** + * {@inheritDoc} + */ @Override public boolean isLooping() { return audios.stream().anyMatch(aw -> aw.audio.isLooping()); } + /** + * {@inheritDoc} + */ @Override public void setVolume( double pVolume ) { volume = (float) pVolume; audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume)); } + /** + * {@inheritDoc} + */ @Override - public void playOnce() { - audios.stream().forEach(aw -> aw.audio.playOnce()); + public double getVolume() { + return volume; } + /** + * {@inheritDoc} + */ @Override - public void playOnceAndWait() { - audios.stream().forEach(aw -> aw.audio.playOnce()); + public void play() { + audios.stream().forEach(aw -> aw.audio.play()); + } + + /** + * {@inheritDoc} + */ + @Override + public void playAndWait() { + audios.stream().forEach(aw -> aw.audio.play()); while( audios.stream().anyMatch(aw -> aw.audio.isPlaying()) ) { try { Thread.sleep(10); @@ -83,16 +106,25 @@ public class Mixer implements Audio { } } + /** + * {@inheritDoc} + */ @Override public void loop() { audios.stream().forEach(aw -> aw.audio.loop()); } + /** + * {@inheritDoc} + */ @Override public void stop() { audios.stream().forEach(aw -> aw.audio.stop()); } + /** + * {@inheritDoc} + */ @Override public void dispose() { if( isPlaying() ) { @@ -101,6 +133,18 @@ public class Mixer implements Audio { audios.stream().forEach(aw -> aw.audio.dispose()); } + /** + * Ändert die Lautstärke aller hinzugefügten Audiomedien in der angegebenen + * Zeit schrittweise, bis die angegebene Lautstärke erreicht ist. + *

+ * Zu beachten ist, dass die Lautstärke des Mixers angepasst wird. Das + * bedeutet, dass die Lautstärke der hinzugefügten Medien mit ihrem + * Lautstärkefaktor multipliziert werden. Die Medien haben am Ende also + * nicht unbedingt die Lautstärke {@code to}. + * + * @param to Der Zielwert für die Lautstärke. + * @param time Die Zeit, nach der die Änderung abgeschlossen sein soll. + */ public void fade( final double to, final int time ) { TaskRunner.run(new Runnable() { @Override @@ -108,9 +152,6 @@ public class Mixer implements Audio { final long start = System.currentTimeMillis(); double t = 0.0; double from = volume; - if( !isPlaying() ) { - playOnce(); - } do { setVolume(Constants.interpolate(from, to, t)); t = (double) (System.currentTimeMillis() - start) / (double) time; diff --git a/src/main/java/schule/ngb/zm/media/Music.java b/src/main/java/schule/ngb/zm/media/Music.java index fa03cf3..72ad34c 100644 --- a/src/main/java/schule/ngb/zm/media/Music.java +++ b/src/main/java/schule/ngb/zm/media/Music.java @@ -1,43 +1,82 @@ package schule.ngb.zm.media; import schule.ngb.zm.tasks.TaskRunner; +import schule.ngb.zm.util.Log; import schule.ngb.zm.util.ResourceStreamProvider; +import schule.ngb.zm.util.Validator; import javax.sound.sampled.*; -import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; +/** + * Ein Musikstück, dass im Projekt abgespielt werden soll. + *

+ * 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. + */ public class Music implements Audio { // size of the byte buffer used to read/write the audio stream private static final int BUFFER_SIZE = 4096; + /** + * Ob der Sound gerade abgespielt wird. + */ private boolean playing = false; + /** + * Ob der Sound gerade in einer Schleife abgespielt wird. + */ private boolean looping = false; + /** + * Die Quelle des Musikstücks. + */ private String audioSource; + /** + * Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet + * wurde. Sonst {@code null}. + */ private AudioInputStream audioStream; + /** + * Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst + * {@code null}. + */ private SourceDataLine audioLine; + /** + * Die Lautstärke der Musik. + */ private float volume = 0.8f; + /** + * Erstellt eine Musik aus der angegebenen Datei oder Webadresse. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @throws NullPointerException Falls die Quelle {@code null} ist. + */ public Music( String source ) { + Validator.requireNotNull(source); this.audioSource = source; } + /** + * {@inheritDoc} + */ @Override public boolean isPlaying() { return playing; } + /** + * {@inheritDoc} + */ @Override public boolean isLooping() { if( !playing ) { @@ -46,6 +85,9 @@ public class Music implements Audio { return looping; } + /** + * {@inheritDoc} + */ @Override public void setVolume( double volume ) { this.volume = (float) volume; @@ -54,6 +96,18 @@ public class Music implements Audio { } } + /** + * {@inheritDoc} + */ + @Override + public double getVolume() { + return volume; + } + + /** + * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen + * anzuwenden. + */ private void applyVolume() { FloatControl gainControl = (FloatControl) audioLine.getControl(FloatControl.Type.MASTER_GAIN); @@ -63,8 +117,11 @@ public class Music implements Audio { gainControl.setValue(vol); } + /** + * {@inheritDoc} + */ @Override - public void playOnce() { + public void play() { if( openLine() ) { TaskRunner.run(new Runnable() { @Override @@ -75,19 +132,28 @@ public class Music implements Audio { } } + /** + * {@inheritDoc} + */ @Override - public void playOnceAndWait() { + public void playAndWait() { if( openLine() ) { stream(); } } + /** + * {@inheritDoc} + */ @Override public void loop() { looping = true; - playOnce(); + play(); } + /** + * {@inheritDoc} + */ @Override public void stop() { playing = false; @@ -95,6 +161,9 @@ public class Music implements Audio { dispose(); } + /** + * {@inheritDoc} + */ @Override public void dispose() { if( audioLine != null ) { @@ -112,7 +181,8 @@ public class Music implements Audio { if( audioStream != null ) { audioStream.close(); } - } catch( IOException ex ) {} + } catch( IOException ex ) { + } audioLine = null; audioStream = null; @@ -126,15 +196,14 @@ public class Music implements Audio { int bytesRead = -1; try { - while (playing && (bytesRead = audioStream.read(bytesBuffer)) != -1) { + while( playing && (bytesRead = audioStream.read(bytesBuffer)) != -1 ) { audioLine.write(bytesBuffer, 0, bytesRead); } audioLine.drain(); audioLine.stop(); } catch( IOException ex ) { - LOGGER.warning("Error while playing Music source <" + audioSource + ">"); - LOGGER.throwing("Music", "stream", ex); + LOG.warn(ex, "Error while playing Music source <%s>", audioSource); } // Wait for the remaining audio to play @@ -163,7 +232,7 @@ public class Music implements Audio { final int ch = format.getChannels(); final float rate = format.getSampleRate(); - AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch*2, rate, false); + AudioFormat outFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false); DataLine.Info info = new DataLine.Info(SourceDataLine.class, outFormat); @@ -174,14 +243,14 @@ public class Music implements Audio { audioStream = AudioSystem.getAudioInputStream(outFormat, inStream); return true; } else { - LOGGER.warning("Sound source <" + audioSource + "> could not be played: No audio source found."); + LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource); } } catch( UnsupportedAudioFileException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: The specified audio file is not supported.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource); } catch( LineUnavailableException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Audio line for playing back is unavailable.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource); } catch( IOException ex ) { - LOGGER.log(Level.WARNING, "Sound source <" + audioSource + "> could not be played: Error playing the audio file.", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource); } return false; } @@ -201,7 +270,6 @@ public class Music implements Audio { } } - //private static final Logger LOGGER = Logger.getLogger("schule.ngb.zm.media.Music"); - private static final Logger LOGGER = Logger.getLogger(Music.class.getName()); + private static final Log LOG = Log.getLogger(Music.class); } diff --git a/src/main/java/schule/ngb/zm/media/Sound.java b/src/main/java/schule/ngb/zm/media/Sound.java index ceda917..ece650d 100644 --- a/src/main/java/schule/ngb/zm/media/Sound.java +++ b/src/main/java/schule/ngb/zm/media/Sound.java @@ -1,35 +1,84 @@ package schule.ngb.zm.media; +import schule.ngb.zm.util.Log; import schule.ngb.zm.util.ResourceStreamProvider; +import schule.ngb.zm.util.Validator; import javax.sound.sampled.*; import java.io.IOException; import java.io.InputStream; import java.util.logging.Logger; +/** + * Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden. + *

+ * 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. + *

+ * Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die + * KLasse {@link Music} an. + */ +@SuppressWarnings( "unused" ) public class Sound implements Audio { + /** + * Ob der Sound gerade abgespielt wird. + */ private boolean playing = false; + /** + * Ob der Sound gerade in einer Schleife abgespielt wird. + */ private boolean looping = false; + /** + * Die Quelle des Musikstücks. + */ private String audioSource; + /** + * Der Clip, falls er schon geladen wurde, sonst {@code null}. + */ private Clip audioClip; + /** + * Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen + * freigegeben werden sollen. + */ private boolean disposeAfterPlay = false; + /** + * Die Lautstärke des Clips. + */ private float volume = 0.8f; + /** + * Erstellt einen Sound aus der angegebene Quelle. + * + * @param source Ein Dateipfad oder eine Webadresse. + * @throws NullPointerException Falls die Quelle {@code null} ist. + */ public Sound( String source ) { + Validator.requireNotNull(source); this.audioSource = source; } + /** + * {@inheritDoc} + */ + @Override public boolean isPlaying() { // return audioClip != null && audioClip.isRunning(); return playing; } + /** + * {@inheritDoc} + */ + @Override public boolean isLooping() { if( !playing ) { looping = false; @@ -37,6 +86,10 @@ public class Sound implements Audio { return looping; } + /** + * {@inheritDoc} + */ + @Override public void setVolume( double volume ) { this.volume = (float) volume; if( audioClip != null ) { @@ -44,6 +97,18 @@ public class Sound implements Audio { } } + /** + * {@inheritDoc} + */ + @Override + public double getVolume() { + return volume; + } + + /** + * Interne Methode, um die gesetzte Lautstärke vor dem Abspielen + * anzuwenden. + */ private void applyVolume() { FloatControl gainControl = (FloatControl) audioClip.getControl(FloatControl.Type.MASTER_GAIN); @@ -53,6 +118,10 @@ public class Sound implements Audio { gainControl.setValue(vol); } + /** + * {@inheritDoc} + */ + @Override public void stop() { looping = false; if( audioClip.isRunning() ) { @@ -61,6 +130,10 @@ public class Sound implements Audio { playing = false; } + /** + * {@inheritDoc} + */ + @Override public void play() { if( this.openClip() ) { audioClip.start(); @@ -68,16 +141,10 @@ public class Sound implements Audio { } } - public void playOnce() { - disposeAfterPlay = true; - play(); - } - - public void playOnceAndWait() { - disposeAfterPlay = true; - playAndWait(); - } - + /** + * {@inheritDoc} + */ + @Override public void playAndWait() { this.play(); @@ -94,26 +161,73 @@ public class Sound implements Audio { audioClip.close(); } + /** + * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips + * frei. + *

+ * Der Aufruf ist effektiv gleich zu + *


+	 * clip.playAndWait();
+	 * clip.dispose();
+	 * 
+ * allerdings wird der aufrufende Thread nicht blockiert und + * {@link #dispose()} automatisch am Ende aufgerufen. + */ + public void playOnce() { + disposeAfterPlay = true; + play(); + } + + /** + * Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips + * frei. + *

+ * Der Aufruf entspricht + *


+	 * clip.playAndWait();
+	 * clip.dispose();
+	 * 
+ */ + public void playOnceAndWait() { + disposeAfterPlay = true; + playAndWait(); + } + + /** + * {@inheritDoc} + */ + @Override public void loop() { loop(Clip.LOOP_CONTINUOUSLY); } + /** + * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt + * die Wiedergabe dann. + * @param count Anzahl der Wiederholungen. + */ public void loop( int count ) { - int loopCount = count; - if( loopCount != Clip.LOOP_CONTINUOUSLY ) { - if( loopCount <= 0 ) { - return; + if( count > 0 ) { + int loopCount = count; + if( loopCount != Clip.LOOP_CONTINUOUSLY ) { + if( loopCount <= 0 ) { + return; + } + // Adjust Number of loops + loopCount -= 1; + } + if( openClip() ) { + looping = true; + audioClip.loop(loopCount); + playing = true; } - // Adjust Number of loops - loopCount -= 1; - } - if( openClip() ) { - looping = true; - audioClip.loop(loopCount); - playing = true; } } + /** + * {@inheritDoc} + */ + @Override public void dispose() { if( audioClip != null ) { if( audioClip.isRunning() ) { @@ -150,17 +264,14 @@ public class Sound implements Audio { applyVolume(); return true; } else { - LOGGER.warning("Sound source " + audioSource + " could not be played: No audio source found."); + LOG.warn("Sound source <%s> could not be played: No audio source found.", audioSource); } } catch( UnsupportedAudioFileException ex ) { - LOGGER.warning("Sound source " + audioSource + " could not be played: The specified audio file is not supported."); - LOGGER.throwing("Sound", "openClip", ex); + LOG.warn(ex, "Sound source <%s> could not be played: The specified audio file is not supported.", audioSource); } catch( LineUnavailableException ex ) { - LOGGER.warning("Sound source " + audioSource + " could not be played: Audio line for playing back is unavailable."); - LOGGER.throwing("Sound", "openClip", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Audio line for playing back is unavailable.", audioSource); } catch( IOException ex ) { - LOGGER.warning("Sound source " + audioSource + " could not be played: Error playing the audio file."); - LOGGER.throwing("Sound", "openClip", ex); + LOG.warn(ex, "Sound source <%s> could not be played: Error playing the audio file.", audioSource); } return false; } @@ -180,6 +291,11 @@ 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. + */ private void playbackStopped() { playing = false; if( disposeAfterPlay ) { @@ -188,17 +304,6 @@ public class Sound implements Audio { } } - /* - public void addLineListener( LineListener listener ) { - if( audioClip == null ) { - openClip(); - } - if( audioClip != null ) { - audioClip.addLineListener(listener); - } - } - */ - - private static final Logger LOGGER = Logger.getLogger(Sound.class.getName()); + private static final Log LOG = Log.getLogger(Sound.class); }