Dokumentation

This commit is contained in:
ngb 2022-12-10 11:04:48 +01:00
parent 3cf7871591
commit cb0ee9c842
4 changed files with 173 additions and 134 deletions

View File

@ -1,10 +1,21 @@
package schule.ngb.zm.media;
/**
* Interface für Audio-Medien.
* Schnittstelle für Audio-Medien.
*
* <h4>MP3-Dateien verwenden</h4>
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
* Details zur Verwendung können in der <a
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
* der Zeichenmaschine</a> 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 {
* <p>
* 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 <a
@ -39,7 +50,7 @@ public interface Audio {
void setVolume( double volume );
/**
* Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück.
* Liefert die aktuelle Lautstärke dieses Mediums.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 );

View File

@ -14,20 +14,20 @@ import schule.ngb.zm.util.events.Listener;
public interface AudioListener extends Listener<Audio> {
/**
* 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 );

View File

@ -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.
* <p>
* 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.
* <p>
* Daher ist es nicht möglich, die länge der Musik im Vorfeld abzufragen oder zu
* einer bestimmten Stelle im Stream zu springen.
*
* <h4>MP3-Dateien verwenden</h4>
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
* Details zur Verwendung können in der <a
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
* der Zeichenmaschine</a> 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<Audio, AudioListener> 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.
* <p>
* 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.
* <p>
* 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<Audio, AudioListener> 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;
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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.
*
* <h4>MP3-Dateien verwenden</h4>
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
* Details zur Verwendung können in der <a
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
* der Zeichenmaschine</a> 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<Audio, AudioListener> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Audio, AudioListener> 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;
}