mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
Added more flexible dependency injection to MusicManager for unittesting. Moved to event driven design for less complex code and lower runtime complexity.
This commit is contained in:
@@ -1,17 +1,12 @@
|
|||||||
package org.toop;
|
package org.toop;
|
||||||
|
|
||||||
import javafx.scene.media.MediaPlayer;
|
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.framework.audio.AudioEventListener;
|
import org.toop.framework.audio.*;
|
||||||
import org.toop.framework.audio.AudioVolumeManager;
|
|
||||||
import org.toop.framework.audio.MusicManager;
|
|
||||||
import org.toop.framework.audio.SoundEffectManager;
|
|
||||||
import org.toop.framework.networking.NetworkingClientManager;
|
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.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) {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.toop.framework.audio;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import org.toop.framework.audio.interfaces.Dispatcher;
|
||||||
|
|
||||||
|
// TODO isn't specific to audio
|
||||||
|
public class JavaFXDispatcher implements Dispatcher {
|
||||||
|
@Override
|
||||||
|
public void run(Runnable task) {
|
||||||
|
Platform.runLater(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,40 @@
|
|||||||
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.audio.interfaces.Dispatcher;
|
||||||
import org.toop.framework.resource.ResourceManager;
|
import org.toop.framework.resource.ResourceManager;
|
||||||
import org.toop.framework.resource.resources.BaseResource;
|
import org.toop.framework.resource.resources.BaseResource;
|
||||||
import org.toop.framework.resource.types.AudioResource;
|
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<T extends AudioResource> implements org.toop.framework.audio.interfaces.MusicManager<T> {
|
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 Class<T> type;
|
|
||||||
private final List<T> backgroundMusic = new LinkedList<>();
|
private final List<T> backgroundMusic = new LinkedList<>();
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
|
private final List<T> resources;
|
||||||
private int playingIndex = 0;
|
private int playingIndex = 0;
|
||||||
private ScheduledExecutorService scheduler;
|
private boolean playing = false;
|
||||||
|
|
||||||
public MusicManager(Class<T> type) {
|
public MusicManager(Class<T> type) {
|
||||||
this.type = type;
|
this.dispatcher = new JavaFXDispatcher();
|
||||||
|
this.resources = new ArrayList<>(ResourceManager.getAllOfType((Class<? extends BaseResource>) type)
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdownScheduler));
|
.stream()
|
||||||
|
.map(e -> (T) e.getResource())
|
||||||
|
.toList());
|
||||||
|
createShuffled();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void increasePlayingIndex() {
|
// Used in unit testing
|
||||||
playingIndex = (playingIndex + 1) % backgroundMusic.size();
|
MusicManager(Class<T> type, Dispatcher dispatcher, ResourceManager resourceManager) {
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
this.resources = new ArrayList<>(resourceManager.getAllOfType((Class<? extends BaseResource>) type)
|
||||||
|
.stream()
|
||||||
|
.map(e -> (T) e.getResource())
|
||||||
|
.toList());
|
||||||
|
createShuffled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -37,52 +46,61 @@ public class MusicManager<T extends AudioResource> implements org.toop.framework
|
|||||||
backgroundMusic.add(musicAsset);
|
backgroundMusic.add(musicAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdownScheduler() {
|
private void createShuffled() {
|
||||||
if (scheduler != null && !scheduler.isShutdown()) {
|
backgroundMusic.clear();
|
||||||
scheduler.shutdownNow();
|
Collections.shuffle(resources);
|
||||||
scheduler = null;
|
backgroundMusic.addAll(resources);
|
||||||
logger.debug("MusicManager scheduler shut down.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
shutdownScheduler();
|
|
||||||
Platform.runLater(() -> backgroundMusic.forEach(T::stop));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void play() {
|
public void play() {
|
||||||
backgroundMusic.clear();
|
if (playing) {
|
||||||
@SuppressWarnings("unchecked")
|
logger.warn("MusicManager is already playing.");
|
||||||
List<T> resources = new ArrayList<>(ResourceManager.getAllOfType((Class<? extends BaseResource>) type)
|
return;
|
||||||
.stream()
|
}
|
||||||
.map(e -> (T) e.getResource())
|
|
||||||
.toList());
|
|
||||||
Collections.shuffle(resources);
|
|
||||||
backgroundMusic.addAll(resources);
|
|
||||||
|
|
||||||
if (backgroundMusic.isEmpty()) return;
|
if (backgroundMusic.isEmpty()) return;
|
||||||
|
|
||||||
shutdownScheduler();
|
playingIndex = 0;
|
||||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
playing = true;
|
||||||
|
playCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
AtomicReference<T> current = new AtomicReference<>(backgroundMusic.get(playingIndex));
|
private void playCurrentTrack() {
|
||||||
|
if (playingIndex >= backgroundMusic.size()) {
|
||||||
|
playingIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
T current = backgroundMusic.get(playingIndex);
|
||||||
T first = current.get();
|
|
||||||
if (!first.isPlaying()) first.play();
|
if (current == null) {
|
||||||
|
logger.error("Current track is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatcher.run(() -> {
|
||||||
|
current.play();
|
||||||
|
|
||||||
|
current.setOnEnd(() -> {
|
||||||
|
playingIndex++;
|
||||||
|
playCurrentTrack();
|
||||||
|
});
|
||||||
|
|
||||||
|
current.setOnError(() -> {
|
||||||
|
logger.error("Error playing track: {}", current);
|
||||||
|
backgroundMusic.remove(current);
|
||||||
|
if (!backgroundMusic.isEmpty()) {
|
||||||
|
playCurrentTrack();
|
||||||
|
} else {
|
||||||
|
playing = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
scheduler.scheduleAtFixedRate(() -> {
|
public void stop() {
|
||||||
T track = current.get();
|
if (!playing) return;
|
||||||
if (!track.isPlaying()) {
|
|
||||||
increasePlayingIndex();
|
playing = false;
|
||||||
T next = backgroundMusic.get(playingIndex);
|
dispatcher.run(() -> backgroundMusic.forEach(T::stop));
|
||||||
current.set(next);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (!next.isPlaying()) next.play();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 500, 500, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package org.toop.framework.audio.interfaces;
|
||||||
|
|
||||||
|
// TODO isn't specific to audio
|
||||||
|
public interface Dispatcher {
|
||||||
|
void run(Runnable task);
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ 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.
|
||||||
@@ -52,12 +51,20 @@ import org.toop.framework.resource.types.AudioResource;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class ResourceManager {
|
public class ResourceManager {
|
||||||
private static final Logger logger = LogManager.getLogger(ResourceManager.class);
|
private static final Logger logger = LogManager.getLogger(ResourceManager.class);
|
||||||
private static final Map<String, ResourceMeta<? extends BaseResource>> assets =
|
private static final Map<String, ResourceMeta<? extends BaseResource>> assets =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
private static ResourceManager instance;
|
||||||
|
|
||||||
private ResourceManager() {}
|
private ResourceManager() {}
|
||||||
|
|
||||||
|
public static ResourceManager getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ResourceManager();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all assets from a given {@link ResourceLoader} into the manager.
|
* Loads all assets from a given {@link ResourceLoader} into the manager.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -62,8 +62,13 @@ public class MusicAsset extends BaseResource implements LoadableResource, AudioR
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public void setOnEnd(Runnable run) {
|
||||||
return isPlaying;
|
mediaPlayer.setOnEndOfMedia(run);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnError(Runnable run) {
|
||||||
|
mediaPlayer.setOnError(run);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -101,9 +101,13 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public void setOnEnd(Runnable run) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnError(Runnable run) {
|
||||||
// TODO
|
// TODO
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package org.toop.framework.resource.types;
|
|||||||
|
|
||||||
public interface AudioResource {
|
public interface AudioResource {
|
||||||
void updateVolume(double volume);
|
void updateVolume(double volume);
|
||||||
boolean isPlaying();
|
// boolean isPlaying();
|
||||||
void play();
|
void play();
|
||||||
void stop();
|
void stop();
|
||||||
|
void setOnEnd(Runnable run);
|
||||||
|
void setOnError(Runnable run);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user