Made all of the updated classes more generic for better flexibility in unittesting

This commit is contained in:
Bas de Jong
2025-10-11 19:31:55 +02:00
parent b101734fd7
commit 1ecdb9a555
15 changed files with 167 additions and 91 deletions

View File

@@ -10,6 +10,8 @@ import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.networking.NetworkingInitializationException;
import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceLoader;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
import org.toop.framework.resource.resources.MusicAsset;
import org.toop.framework.resource.resources.SoundEffectAsset;
public final class Main { public final class Main {
static void main(String[] args) { static void main(String[] args) {
@@ -23,7 +25,7 @@ public final class Main {
new Thread(() -> { new Thread(() -> {
AudioEventListener<?, ?> a = AudioEventListener<?, ?> a =
new AudioEventListener<>( new AudioEventListener<>(
new MusicManager(), new MusicManager<>(MusicAsset.class),
new SoundEffectManager(), new SoundEffectManager(),
new AudioVolumeManager() new AudioVolumeManager()
); a.initListeners(); ); a.initListeners();

View File

@@ -126,6 +126,7 @@ public final class App extends Application {
} }
public static void quit() { public static void quit() {
new EventFlow().addPostEvent(new AudioEvents.StopAudioManager()).postEvent();
stage.close(); stage.close();
} }

View File

@@ -24,6 +24,7 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
public void initListeners() { public void initListeners() {
new EventFlow() new EventFlow()
.listen(this::handleStopMusicManager)
.listen(this::handlePlaySound) .listen(this::handlePlaySound)
.listen(this::handleStopSound) .listen(this::handleStopSound)
.listen(this::handleMusicStart) .listen(this::handleMusicStart)
@@ -35,6 +36,10 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
.listen(this::handleGetMusicVolume); .listen(this::handleGetMusicVolume);
} }
private void handleStopMusicManager(AudioEvents.StopAudioManager event) {
this.musicManager.stop();
}
private void handlePlaySound(AudioEvents.PlayEffect event) { private void handlePlaySound(AudioEvents.PlayEffect event) {
this.soundEffectManager.play(event.fileName(), event.loop()); this.soundEffectManager.play(event.fileName(), event.loop());
} }
@@ -48,15 +53,15 @@ public class AudioEventListener<T extends AudioResource, K extends AudioResource
} }
private void handleVolumeChange(AudioEvents.ChangeVolume event) { private void handleVolumeChange(AudioEvents.ChangeVolume event) {
this.audioVolumeManager.setVolume(event.newVolume(), soundEffectManager, musicManager); this.audioVolumeManager.setVolume(event.newVolume(), VolumeTypes.VOLUME, soundEffectManager, musicManager);
} }
private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) { private void handleFxVolumeChange(AudioEvents.ChangeFxVolume event) {
this.audioVolumeManager.setFxVolume(event.newVolume(), soundEffectManager); this.audioVolumeManager.setVolume(event.newVolume(), VolumeTypes.FX, soundEffectManager);
} }
private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) { private void handleMusicVolumeChange(AudioEvents.ChangeMusicVolume event) {
this.audioVolumeManager.setMusicVolume(event.newVolume(), musicManager); this.audioVolumeManager.setVolume(event.newVolume(), VolumeTypes.MUSIC, musicManager);
} }
private void handleGetVolume(AudioEvents.GetCurrentVolume event) { private void handleGetVolume(AudioEvents.GetCurrentVolume event) {

View File

@@ -4,6 +4,9 @@ import org.toop.framework.audio.interfaces.AudioManager;
import org.toop.framework.audio.interfaces.VolumeManager; import org.toop.framework.audio.interfaces.VolumeManager;
import org.toop.framework.resource.types.AudioResource; import org.toop.framework.resource.types.AudioResource;
import java.util.Arrays;
import java.util.Objects;
public class AudioVolumeManager implements VolumeManager { public class AudioVolumeManager implements VolumeManager {
private double volume = 0.0; private double volume = 0.0;
private double fxVolume = 0.0; private double fxVolume = 0.0;
@@ -19,32 +22,28 @@ public class AudioVolumeManager implements VolumeManager {
return Math.min(1.0, Math.max(0.0, volume / 100)); return Math.min(1.0, Math.max(0.0, volume / 100));
} }
@SafeVarargs
@Override @Override
public <T extends AudioResource, K extends AudioResource> void setVolume( public final void setVolume(double newVolume, VolumeTypes type, AudioManager<? extends AudioResource>... managers) {
double newVolume, AudioManager<T> sm, AudioManager<K> mm) { double limitedVolume = limitVolume(newVolume);
this.volume = limitVolume(newVolume);
for (T clip : sm.getActiveAudio()) {
this.updateVolume(clip, fxVolume * volume);
}
for (K mediaPlayer : mm.getActiveAudio()) {
this.updateVolume(mediaPlayer, musicVolume * volume);
}
}
@Override switch (type) {
public <T extends AudioResource> void setFxVolume(double newVolume, AudioManager<T> sm) { case FX -> fxVolume = limitedVolume;
this.fxVolume = limitVolume(newVolume); case MUSIC -> musicVolume = limitedVolume;
for (T clip : sm.getActiveAudio()) { default -> volume = limitedVolume;
this.updateVolume(clip, fxVolume * volume);
} }
}
@Override double effectiveVolume = switch (type) {
public <T extends AudioResource> void setMusicVolume(double newVolume, AudioManager<T> mm) { case FX -> fxVolume * volume;
this.musicVolume = limitVolume(newVolume); case MUSIC -> musicVolume * volume;
for (T mediaPlayer : mm.getActiveAudio()) { default -> volume;
this.updateVolume(mediaPlayer, musicVolume * volume); };
}
Arrays.stream(managers)
.filter(Objects::nonNull)
.forEach(manager ->
manager.getActiveAudio().forEach(aud -> updateVolume(aud, effectiveVolume))
);
} }
@Override @Override

View File

@@ -1,81 +1,88 @@
package org.toop.framework.audio; package org.toop.framework.audio;
import javafx.application.Platform;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.ResourceManager;
import org.toop.framework.resource.resources.MusicAsset; import org.toop.framework.resource.resources.BaseResource;
import org.toop.framework.resource.types.AudioResource;
import java.util.*; import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class MusicManager implements org.toop.framework.audio.interfaces.MusicManager<MusicAsset> { public class MusicManager<T extends AudioResource> implements org.toop.framework.audio.interfaces.MusicManager<T> {
private static final Logger logger = LogManager.getLogger(MusicManager.class); private static final Logger logger = LogManager.getLogger(MusicManager.class);
private final List<MusicAsset> backgroundMusic = new LinkedList<>(); private final Class<T> type;
private final List<T> backgroundMusic = new LinkedList<>();
private int playingIndex = 0; private int playingIndex = 0;
private boolean playing = false; private ScheduledExecutorService scheduler;
public MusicManager() {} public MusicManager(Class<T> type) {
this.type = type;
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdownScheduler));
}
private void increasePlayingIndex() {
playingIndex = (playingIndex + 1) % backgroundMusic.size();
}
@Override @Override
public Collection<MusicAsset> getActiveAudio() { public Collection<T> getActiveAudio() {
return backgroundMusic; return backgroundMusic;
} }
private void addBackgroundMusic(MusicAsset musicAsset) { private void addBackgroundMusic(T musicAsset) {
backgroundMusic.add(musicAsset); backgroundMusic.add(musicAsset);
} }
private void shutdownScheduler() {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdownNow();
scheduler = null;
logger.debug("MusicManager scheduler shut down.");
}
}
@Override
public void stop() {
shutdownScheduler();
Platform.runLater(() -> backgroundMusic.forEach(T::stop));
}
public void play() { public void play() {
if (playing) {
logger.warn("MusicManager is already playing.");
return;
}
backgroundMusic.clear(); backgroundMusic.clear();
List<MusicAsset> shuffledArray = @SuppressWarnings("unchecked")
new ArrayList<>( List<T> resources = new ArrayList<>(ResourceManager.getAllOfType((Class<? extends BaseResource>) type)
ResourceManager.getAllOfType(MusicAsset.class).stream() .stream()
.map(ma -> .map(e -> (T) e.getResource())
initMediaPlayer(ma.getResource())) .toList());
.toList()); Collections.shuffle(resources);
Collections.shuffle(shuffledArray); backgroundMusic.addAll(resources);
backgroundMusic.addAll(shuffledArray);
backgroundMusicPlayer();
}
private void backgroundMusicPlayer() { if (backgroundMusic.isEmpty()) return;
if (playingIndex >= backgroundMusic.size()) { shutdownScheduler();
playingIndex = 0; scheduler = Executors.newSingleThreadScheduledExecutor();
}
MusicAsset ma = backgroundMusic.get(playingIndex); AtomicReference<T> current = new AtomicReference<>(backgroundMusic.get(playingIndex));
if (ma == null) { Platform.runLater(() -> {
logger.error("Background music player is null. Queue: {}", T first = current.get();
backgroundMusic.stream().map(e -> e.getMediaPlayer().getMedia().getSource())); if (!first.isPlaying()) first.play();
return;
}
logger.info("Background music player is playing: {}", ma.getMediaPlayer().getMedia().getSource()); //TODO shorten to name
ma.getMediaPlayer().play();
this.playing = true;
}
private MusicAsset initMediaPlayer(MusicAsset ma) {
ma.getMediaPlayer().setOnEndOfMedia(() -> ma.getMediaPlayer().stop());
ma.getMediaPlayer().setOnError( () -> {
logger.error("Error playing music: {}", ma.getMediaPlayer().getError()); // TODO
backgroundMusic.remove(ma);
ma.getMediaPlayer().stop();
}); });
ma.getMediaPlayer().setOnStopped( () -> { scheduler.scheduleAtFixedRate(() -> {
ma.getMediaPlayer().stop(); T track = current.get();
playingIndex++; if (!track.isPlaying()) {
this.playing = false; increasePlayingIndex();
backgroundMusicPlayer(); T next = backgroundMusic.get(playingIndex);
}); current.set(next);
Platform.runLater(() -> {
return ma; if (!next.isPlaying()) next.play();
});
}
}, 500, 500, TimeUnit.MILLISECONDS);
} }
} }

View File

@@ -17,11 +17,11 @@ public class SoundEffectManager implements org.toop.framework.audio.interfaces.S
@Override @Override
public void play(String name, boolean loop) { public void play(String name, boolean loop) {
//TODO
} }
@Override @Override
public void stop(long clipId) { public void stop(long clipId) {
//TODO
} }
} }

View File

@@ -0,0 +1,7 @@
package org.toop.framework.audio;
public enum VolumeTypes {
VOLUME,
FX,
MUSIC,
}

View File

@@ -6,6 +6,8 @@ import org.toop.framework.eventbus.events.EventWithoutSnowflake;
import org.toop.framework.eventbus.events.EventsBase; import org.toop.framework.eventbus.events.EventsBase;
public class AudioEvents extends EventsBase { public class AudioEvents extends EventsBase {
public record StopAudioManager() implements EventWithoutSnowflake {}
/** Starts playing a sound. */ /** Starts playing a sound. */
public record PlayEffect(String fileName, boolean loop) implements EventWithoutSnowflake {} public record PlayEffect(String fileName, boolean loop) implements EventWithoutSnowflake {}

View File

@@ -1,5 +1,7 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
import org.toop.framework.audio.VolumeTypes;
import java.util.Collection; import java.util.Collection;
public interface AudioManager<T> { public interface AudioManager<T> {

View File

@@ -4,4 +4,5 @@ import org.toop.framework.resource.types.AudioResource;
public interface MusicManager<T extends AudioResource> extends AudioManager<T> { public interface MusicManager<T extends AudioResource> extends AudioManager<T> {
void play(); void play();
void stop();
} }

View File

@@ -1,11 +1,10 @@
package org.toop.framework.audio.interfaces; package org.toop.framework.audio.interfaces;
import org.toop.framework.audio.VolumeTypes;
import org.toop.framework.resource.types.AudioResource; import org.toop.framework.resource.types.AudioResource;
public interface VolumeManager { public interface VolumeManager {
<T extends AudioResource, K extends AudioResource> void setVolume(double newVolume, AudioManager<T> sm, AudioManager<K> mm); void setVolume(double newVolume, VolumeTypes types, AudioManager<? extends AudioResource>... ams);
<T extends AudioResource> void setFxVolume(double newVolume, AudioManager<T> sm);
<T extends AudioResource> void setMusicVolume(double newVolume, AudioManager<T> mm);
double getVolume(); double getVolume();
double getFxVolume(); double getFxVolume();
double getMusicVolume(); double getMusicVolume();

View File

@@ -7,6 +7,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.toop.framework.resource.exceptions.ResourceNotFoundException; import org.toop.framework.resource.exceptions.ResourceNotFoundException;
import org.toop.framework.resource.resources.*; import org.toop.framework.resource.resources.*;
import org.toop.framework.resource.types.AudioResource;
/** /**
* Centralized manager for all loaded assets in the application. * Centralized manager for all loaded assets in the application.
@@ -96,16 +97,19 @@ public class ResourceManager {
* @param <T> the resource type * @param <T> the resource type
* @return a list of assets matching the type * @return a list of assets matching the type
*/ */
public static <T extends BaseResource> ArrayList<ResourceMeta<T>> getAllOfType(Class<T> type) { public static <T extends BaseResource> List<ResourceMeta<T>> getAllOfType(Class<T> type) {
ArrayList<ResourceMeta<T>> list = new ArrayList<>(); List<ResourceMeta<T>> result = new ArrayList<>();
for (ResourceMeta<? extends BaseResource> asset : assets.values()) {
if (type.isInstance(asset.getResource())) { for (ResourceMeta<? extends BaseResource> meta : assets.values()) {
BaseResource res = meta.getResource();
if (type.isInstance(res)) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ResourceMeta<T> typed = (ResourceMeta<T>) asset; ResourceMeta<T> typed = (ResourceMeta<T>) meta;
list.add(typed); result.add(typed);
} }
} }
return list;
return result;
} }
/** /**

View File

@@ -10,6 +10,8 @@ import org.toop.framework.resource.types.LoadableResource;
@FileExtension({"mp3"}) @FileExtension({"mp3"})
public class MusicAsset extends BaseResource implements LoadableResource, AudioResource { public class MusicAsset extends BaseResource implements LoadableResource, AudioResource {
private MediaPlayer mediaPlayer; private MediaPlayer mediaPlayer;
private double volume;
private boolean isPlaying = false;
public MusicAsset(final File audioFile) { public MusicAsset(final File audioFile) {
super(audioFile); super(audioFile);
@@ -20,10 +22,18 @@ public class MusicAsset extends BaseResource implements LoadableResource, AudioR
return mediaPlayer; return mediaPlayer;
} }
private void initPlayer() {
mediaPlayer.setOnEndOfMedia(this::stop);
mediaPlayer.setOnError(this::stop);
mediaPlayer.setOnStopped(() -> isPlaying = false);
}
@Override @Override
public void load() { public void load() {
if (mediaPlayer == null) { if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer(new Media(file.toURI().toString())); mediaPlayer = new MediaPlayer(new Media(file.toURI().toString()));
initPlayer();
mediaPlayer.setVolume(volume);
} }
this.isLoaded = true; this.isLoaded = true;
} }
@@ -48,5 +58,23 @@ public class MusicAsset extends BaseResource implements LoadableResource, AudioR
if (mediaPlayer != null) { if (mediaPlayer != null) {
mediaPlayer.setVolume(volume); mediaPlayer.setVolume(volume);
} }
this.volume = volume;
}
@Override
public boolean isPlaying() {
return isPlaying;
}
@Override
public void play() {
getMediaPlayer().play();
isPlaying = true;
}
@Override
public void stop() {
getMediaPlayer().stop();
isPlaying = false;
} }
} }

View File

@@ -99,4 +99,21 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource,
} }
} }
} }
@Override
public boolean isPlaying() {
// TODO
return false;
}
@Override
public void play() {
// TODO
}
@Override
public void stop() {
// TODO
}
} }

View File

@@ -2,5 +2,7 @@ package org.toop.framework.resource.types;
public interface AudioResource { public interface AudioResource {
void updateVolume(double volume); void updateVolume(double volume);
// TODO play and stop boolean isPlaying();
void play();
void stop();
} }