diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 3372086..e8f3bba 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -10,6 +10,8 @@ import org.toop.framework.networking.NetworkingClientManager; import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceManager; +import org.toop.framework.resource.resources.MusicAsset; +import org.toop.framework.resource.resources.SoundEffectAsset; public final class Main { static void main(String[] args) { @@ -23,7 +25,7 @@ public final class Main { new Thread(() -> { AudioEventListener a = new AudioEventListener<>( - new MusicManager(), + new MusicManager<>(MusicAsset.class), new SoundEffectManager(), new AudioVolumeManager() ); a.initListeners(); diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 4abd328..2d95433 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -126,6 +126,7 @@ public final class App extends Application { } public static void quit() { + new EventFlow().addPostEvent(new AudioEvents.StopAudioManager()).postEvent(); stage.close(); } diff --git a/framework/src/main/java/org/toop/framework/audio/AudioEventListener.java b/framework/src/main/java/org/toop/framework/audio/AudioEventListener.java index a29939f..3326d28 100644 --- a/framework/src/main/java/org/toop/framework/audio/AudioEventListener.java +++ b/framework/src/main/java/org/toop/framework/audio/AudioEventListener.java @@ -24,6 +24,7 @@ public class AudioEventListener void setVolume( - double newVolume, AudioManager sm, AudioManager mm) { - this.volume = limitVolume(newVolume); - for (T clip : sm.getActiveAudio()) { - this.updateVolume(clip, fxVolume * volume); - } - for (K mediaPlayer : mm.getActiveAudio()) { - this.updateVolume(mediaPlayer, musicVolume * volume); - } - } + public final void setVolume(double newVolume, VolumeTypes type, AudioManager... managers) { + double limitedVolume = limitVolume(newVolume); - @Override - public void setFxVolume(double newVolume, AudioManager sm) { - this.fxVolume = limitVolume(newVolume); - for (T clip : sm.getActiveAudio()) { - this.updateVolume(clip, fxVolume * volume); + switch (type) { + case FX -> fxVolume = limitedVolume; + case MUSIC -> musicVolume = limitedVolume; + default -> volume = limitedVolume; } - } - @Override - public void setMusicVolume(double newVolume, AudioManager mm) { - this.musicVolume = limitVolume(newVolume); - for (T mediaPlayer : mm.getActiveAudio()) { - this.updateVolume(mediaPlayer, musicVolume * volume); - } + double effectiveVolume = switch (type) { + case FX -> fxVolume * volume; + case MUSIC -> musicVolume * volume; + default -> volume; + }; + + Arrays.stream(managers) + .filter(Objects::nonNull) + .forEach(manager -> + manager.getActiveAudio().forEach(aud -> updateVolume(aud, effectiveVolume)) + ); } @Override diff --git a/framework/src/main/java/org/toop/framework/audio/MusicManager.java b/framework/src/main/java/org/toop/framework/audio/MusicManager.java index 4b2b87e..bdb0c94 100644 --- a/framework/src/main/java/org/toop/framework/audio/MusicManager.java +++ b/framework/src/main/java/org/toop/framework/audio/MusicManager.java @@ -1,81 +1,88 @@ package org.toop.framework.audio; +import javafx.application.Platform; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; -public class MusicManager implements org.toop.framework.audio.interfaces.MusicManager { +public class MusicManager implements org.toop.framework.audio.interfaces.MusicManager { private static final Logger logger = LogManager.getLogger(MusicManager.class); - private final List backgroundMusic = new LinkedList<>(); + private final Class type; + private final List backgroundMusic = new LinkedList<>(); private int playingIndex = 0; - private boolean playing = false; + private ScheduledExecutorService scheduler; - public MusicManager() {} + public MusicManager(Class type) { + this.type = type; + + Runtime.getRuntime().addShutdownHook(new Thread(this::shutdownScheduler)); + } + + private void increasePlayingIndex() { + playingIndex = (playingIndex + 1) % backgroundMusic.size(); + } @Override - public Collection getActiveAudio() { + public Collection getActiveAudio() { return backgroundMusic; } - private void addBackgroundMusic(MusicAsset musicAsset) { + private void addBackgroundMusic(T 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() { - if (playing) { - logger.warn("MusicManager is already playing."); - return; - } backgroundMusic.clear(); - List shuffledArray = - new ArrayList<>( - ResourceManager.getAllOfType(MusicAsset.class).stream() - .map(ma -> - initMediaPlayer(ma.getResource())) - .toList()); - Collections.shuffle(shuffledArray); - backgroundMusic.addAll(shuffledArray); - backgroundMusicPlayer(); - } + @SuppressWarnings("unchecked") + List resources = new ArrayList<>(ResourceManager.getAllOfType((Class) type) + .stream() + .map(e -> (T) e.getResource()) + .toList()); + Collections.shuffle(resources); + backgroundMusic.addAll(resources); - private void backgroundMusicPlayer() { + if (backgroundMusic.isEmpty()) return; - if (playingIndex >= backgroundMusic.size()) { - playingIndex = 0; - } + shutdownScheduler(); + scheduler = Executors.newSingleThreadScheduledExecutor(); - MusicAsset ma = backgroundMusic.get(playingIndex); + AtomicReference current = new AtomicReference<>(backgroundMusic.get(playingIndex)); - if (ma == null) { - logger.error("Background music player is null. Queue: {}", - backgroundMusic.stream().map(e -> e.getMediaPlayer().getMedia().getSource())); - 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(); + Platform.runLater(() -> { + T first = current.get(); + if (!first.isPlaying()) first.play(); }); - ma.getMediaPlayer().setOnStopped( () -> { - ma.getMediaPlayer().stop(); - playingIndex++; - this.playing = false; - backgroundMusicPlayer(); - }); - - return ma; + scheduler.scheduleAtFixedRate(() -> { + T track = current.get(); + if (!track.isPlaying()) { + increasePlayingIndex(); + T next = backgroundMusic.get(playingIndex); + current.set(next); + Platform.runLater(() -> { + if (!next.isPlaying()) next.play(); + }); + } + }, 500, 500, TimeUnit.MILLISECONDS); } } diff --git a/framework/src/main/java/org/toop/framework/audio/SoundEffectManager.java b/framework/src/main/java/org/toop/framework/audio/SoundEffectManager.java index 00d1a2b..e483768 100644 --- a/framework/src/main/java/org/toop/framework/audio/SoundEffectManager.java +++ b/framework/src/main/java/org/toop/framework/audio/SoundEffectManager.java @@ -17,11 +17,11 @@ public class SoundEffectManager implements org.toop.framework.audio.interfaces.S @Override public void play(String name, boolean loop) { - + //TODO } @Override public void stop(long clipId) { - + //TODO } } diff --git a/framework/src/main/java/org/toop/framework/audio/VolumeTypes.java b/framework/src/main/java/org/toop/framework/audio/VolumeTypes.java new file mode 100644 index 0000000..7a840dc --- /dev/null +++ b/framework/src/main/java/org/toop/framework/audio/VolumeTypes.java @@ -0,0 +1,7 @@ +package org.toop.framework.audio; + +public enum VolumeTypes { + VOLUME, + FX, + MUSIC, +} diff --git a/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java b/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java index aed23ee..1ef84f4 100644 --- a/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java +++ b/framework/src/main/java/org/toop/framework/audio/events/AudioEvents.java @@ -6,6 +6,8 @@ import org.toop.framework.eventbus.events.EventWithoutSnowflake; import org.toop.framework.eventbus.events.EventsBase; public class AudioEvents extends EventsBase { + public record StopAudioManager() implements EventWithoutSnowflake {} + /** Starts playing a sound. */ public record PlayEffect(String fileName, boolean loop) implements EventWithoutSnowflake {} diff --git a/framework/src/main/java/org/toop/framework/audio/interfaces/AudioManager.java b/framework/src/main/java/org/toop/framework/audio/interfaces/AudioManager.java index 9ba7777..9849074 100644 --- a/framework/src/main/java/org/toop/framework/audio/interfaces/AudioManager.java +++ b/framework/src/main/java/org/toop/framework/audio/interfaces/AudioManager.java @@ -1,5 +1,7 @@ package org.toop.framework.audio.interfaces; +import org.toop.framework.audio.VolumeTypes; + import java.util.Collection; public interface AudioManager { diff --git a/framework/src/main/java/org/toop/framework/audio/interfaces/MusicManager.java b/framework/src/main/java/org/toop/framework/audio/interfaces/MusicManager.java index 928bb83..21c495b 100644 --- a/framework/src/main/java/org/toop/framework/audio/interfaces/MusicManager.java +++ b/framework/src/main/java/org/toop/framework/audio/interfaces/MusicManager.java @@ -4,4 +4,5 @@ import org.toop.framework.resource.types.AudioResource; public interface MusicManager extends AudioManager { void play(); + void stop(); } diff --git a/framework/src/main/java/org/toop/framework/audio/interfaces/VolumeManager.java b/framework/src/main/java/org/toop/framework/audio/interfaces/VolumeManager.java index e1bc077..95af48d 100644 --- a/framework/src/main/java/org/toop/framework/audio/interfaces/VolumeManager.java +++ b/framework/src/main/java/org/toop/framework/audio/interfaces/VolumeManager.java @@ -1,11 +1,10 @@ package org.toop.framework.audio.interfaces; +import org.toop.framework.audio.VolumeTypes; import org.toop.framework.resource.types.AudioResource; public interface VolumeManager { - void setVolume(double newVolume, AudioManager sm, AudioManager mm); - void setFxVolume(double newVolume, AudioManager sm); - void setMusicVolume(double newVolume, AudioManager mm); + void setVolume(double newVolume, VolumeTypes types, AudioManager... ams); double getVolume(); double getFxVolume(); double getMusicVolume(); diff --git a/framework/src/main/java/org/toop/framework/resource/ResourceManager.java b/framework/src/main/java/org/toop/framework/resource/ResourceManager.java index 9641733..442e149 100644 --- a/framework/src/main/java/org/toop/framework/resource/ResourceManager.java +++ b/framework/src/main/java/org/toop/framework/resource/ResourceManager.java @@ -7,6 +7,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.toop.framework.resource.exceptions.ResourceNotFoundException; import org.toop.framework.resource.resources.*; +import org.toop.framework.resource.types.AudioResource; /** * Centralized manager for all loaded assets in the application. @@ -96,16 +97,19 @@ public class ResourceManager { * @param the resource type * @return a list of assets matching the type */ - public static ArrayList> getAllOfType(Class type) { - ArrayList> list = new ArrayList<>(); - for (ResourceMeta asset : assets.values()) { - if (type.isInstance(asset.getResource())) { + public static List> getAllOfType(Class type) { + List> result = new ArrayList<>(); + + for (ResourceMeta meta : assets.values()) { + BaseResource res = meta.getResource(); + if (type.isInstance(res)) { @SuppressWarnings("unchecked") - ResourceMeta typed = (ResourceMeta) asset; - list.add(typed); + ResourceMeta typed = (ResourceMeta) meta; + result.add(typed); } } - return list; + + return result; } /** diff --git a/framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java index 45423ee..645bc68 100644 --- a/framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/MusicAsset.java @@ -10,6 +10,8 @@ import org.toop.framework.resource.types.LoadableResource; @FileExtension({"mp3"}) public class MusicAsset extends BaseResource implements LoadableResource, AudioResource { private MediaPlayer mediaPlayer; + private double volume; + private boolean isPlaying = false; public MusicAsset(final File audioFile) { super(audioFile); @@ -20,10 +22,18 @@ public class MusicAsset extends BaseResource implements LoadableResource, AudioR return mediaPlayer; } + private void initPlayer() { + mediaPlayer.setOnEndOfMedia(this::stop); + mediaPlayer.setOnError(this::stop); + mediaPlayer.setOnStopped(() -> isPlaying = false); + } + @Override public void load() { if (mediaPlayer == null) { mediaPlayer = new MediaPlayer(new Media(file.toURI().toString())); + initPlayer(); + mediaPlayer.setVolume(volume); } this.isLoaded = true; } @@ -48,5 +58,23 @@ public class MusicAsset extends BaseResource implements LoadableResource, AudioR if (mediaPlayer != null) { 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; } } diff --git a/framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java b/framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java index b0428e0..6a6a055 100644 --- a/framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java +++ b/framework/src/main/java/org/toop/framework/resource/resources/SoundEffectAsset.java @@ -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 + } + } diff --git a/framework/src/main/java/org/toop/framework/resource/types/AudioResource.java b/framework/src/main/java/org/toop/framework/resource/types/AudioResource.java index 9ea1c21..d0ae626 100644 --- a/framework/src/main/java/org/toop/framework/resource/types/AudioResource.java +++ b/framework/src/main/java/org/toop/framework/resource/types/AudioResource.java @@ -2,5 +2,7 @@ package org.toop.framework.resource.types; public interface AudioResource { void updateVolume(double volume); - // TODO play and stop + boolean isPlaying(); + void play(); + void stop(); }