feat(audio): Refactor audio control integration in AudioStrategy and players for improved option management

This commit is contained in:
extio-de
2025-12-24 21:57:32 +01:00
parent b07f957041
commit 43dff6454e
5 changed files with 77 additions and 53 deletions

View File

@@ -11,7 +11,7 @@ public final class AudioController implements AudioControl {
private final StorageService storageService; private final StorageService storageService;
private AudioOptions audioOptions; private final AudioOptions audioOptions; // Passed as reference to strategies
private AudioStrategy audioStrategy; private AudioStrategy audioStrategy;
@@ -31,12 +31,12 @@ public final class AudioController implements AudioControl {
public AudioController(final StaticResourceService resourceService, final StorageService storageService) { public AudioController(final StaticResourceService resourceService, final StorageService storageService) {
this.storageService = storageService; this.storageService = storageService;
storageService.loadByPath(AudioOptions.class, List.of("audio"), "audioOptions").ifPresentOrElse( this.audioOptions = new AudioOptions();
storageService.loadByPath(AudioOptions.class, List.of("gameEngine"), "audioOptions").ifPresentOrElse(
loadedOptions -> { loadedOptions -> {
this.audioOptions = loadedOptions; this.audioOptions.apply(loadedOptions);
}, },
() -> { () -> {
this.audioOptions = new AudioOptions();
this.storeAudioOptions(); this.storeAudioOptions();
}); });
@@ -63,7 +63,7 @@ public final class AudioController implements AudioControl {
} }
private void storeAudioOptions() { private void storeAudioOptions() {
this.storageService.store(List.of("audio"), "audioOptions", this.audioOptions); this.storageService.store(List.of("gameEngine"), "audioOptions", this.audioOptions);
} }
public void run() { public void run() {
@@ -91,11 +91,11 @@ public final class AudioController implements AudioControl {
.start(this.audioLoader); .start(this.audioLoader);
if (this.audioOptions.getSfxOptions().isSoftwareMixing()) { if (this.audioOptions.getSfxOptions().isSoftwareMixing()) {
this.audioStrategy = new AudioStrategySoftwareMixing(this.audioOptions, this.audioLoader); this.audioStrategy = new AudioStrategySoftwareMixing(this, this.audioOptions, this.audioLoader);
this.useSoftwareMixing = true; this.useSoftwareMixing = true;
} }
else { else {
this.audioStrategy = new AudioStrategyJavaMixer(this.audioOptions, this.audioLoader); this.audioStrategy = new AudioStrategyJavaMixer(this, this.audioOptions, this.audioLoader);
this.useSoftwareMixing = false; this.useSoftwareMixing = false;
} }

View File

@@ -26,6 +26,8 @@ final class AudioPlayerMultiStream implements AudioPlayer {
private static final int MUSIC_FADING_MS = 1000; private static final int MUSIC_FADING_MS = 1000;
private final BlockingQueue<AudioData> queue = new LinkedBlockingQueue<>(20); private final BlockingQueue<AudioData> queue = new LinkedBlockingQueue<>(20);
private final AudioControl audioControl;
private volatile AudioData musicQueue; private volatile AudioData musicQueue;
@@ -51,7 +53,8 @@ final class AudioPlayerMultiStream implements AudioPlayer {
private long notStartedAudioCnt; private long notStartedAudioCnt;
public AudioPlayerMultiStream() { public AudioPlayerMultiStream(AudioControl audioControl) {
this.audioControl = audioControl;
} }
@Override @Override
@@ -288,6 +291,10 @@ final class AudioPlayerMultiStream implements AudioPlayer {
if (++this.notStartedAudioCnt > 5) { if (++this.notStartedAudioCnt > 5) {
this.notStartedAudioCnt = 0; this.notStartedAudioCnt = 0;
var options =this.audioControl.getAudioOptions();
options.getSfxOptions().enable = false;
options.getMusicOptions().enable = false;
this.audioControl.applyAudioOptions(options);
this.exit = true; this.exit = true;
LOGGER.error("Too many audio playback errors, shutting down audio player."); LOGGER.error("Too many audio playback errors, shutting down audio player.");
} }

View File

@@ -24,6 +24,8 @@ final class AudioPlayerSingleStream implements AudioPlayer {
private final BlockingQueue<AudioData> queue = new LinkedBlockingQueue<>(5); private final BlockingQueue<AudioData> queue = new LinkedBlockingQueue<>(5);
private final AudioControl audioControl;
private volatile boolean stopAudio; private volatile boolean stopAudio;
private volatile boolean playing; private volatile boolean playing;
@@ -40,7 +42,8 @@ final class AudioPlayerSingleStream implements AudioPlayer {
private int notStartedAudioCnt; private int notStartedAudioCnt;
public AudioPlayerSingleStream() { public AudioPlayerSingleStream(AudioControl audioControl) {
this.audioControl = audioControl;
} }
@Override @Override
@@ -124,6 +127,10 @@ final class AudioPlayerSingleStream implements AudioPlayer {
if (++this.notStartedAudioCnt > 5) { if (++this.notStartedAudioCnt > 5) {
this.notStartedAudioCnt = 0; this.notStartedAudioCnt = 0;
var options =this.audioControl.getAudioOptions();
options.getSfxOptions().enable = false;
options.getMusicOptions().enable = false;
this.audioControl.applyAudioOptions(options);
this.exit = true; this.exit = true;
LOGGER.error("Too many audio playback errors, shutting down audio player."); LOGGER.error("Too many audio playback errors, shutting down audio player.");
} }

View File

@@ -25,7 +25,9 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
private final static int MUSIC_INITIAL_DELAY_MS = 1000; private final static int MUSIC_INITIAL_DELAY_MS = 1000;
private final AudioOptions clientEngineData; private final AudioControl audioControl;
private final AudioOptions audioOptions;
private final AudioLoader audioLoader; private final AudioLoader audioLoader;
@@ -33,7 +35,7 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
private final List<AudioThread> sfxAudioPlayers = new ArrayList<>(SFX_PLAYERS); private final List<AudioThread> sfxAudioPlayers = new ArrayList<>(SFX_PLAYERS);
private final Set<StaticResource> sfxQueue = new LinkedHashSet<>(); private final Set<StaticResource> sfxQueue = Collections.synchronizedSet(new LinkedHashSet<>());
private final List<AudioPlayerReference> sfxPlayersBySize = new ArrayList<>(SFX_PLAYERS); private final List<AudioPlayerReference> sfxPlayersBySize = new ArrayList<>(SFX_PLAYERS);
@@ -47,15 +49,16 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
private long musicFirstQueued; private long musicFirstQueued;
public AudioStrategyJavaMixer(final AudioOptions clientEngineData, final AudioLoader audioLoader) { public AudioStrategyJavaMixer(final AudioControl audioControl, final AudioOptions audioOptions, final AudioLoader audioLoader) {
this.clientEngineData = clientEngineData; this.audioControl = audioControl;
this.audioOptions = audioOptions;
this.audioLoader = audioLoader; this.audioLoader = audioLoader;
} }
@Override @Override
public void start() { public void start() {
{ {
final var musicAudioPlayer = new AudioPlayerSingleStream(); final var musicAudioPlayer = new AudioPlayerSingleStream(this.audioControl);
final var musicThread = Thread.ofPlatform() final var musicThread = Thread.ofPlatform()
.name("Audio Music") .name("Audio Music")
.daemon(true) .daemon(true)
@@ -65,7 +68,7 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
} }
for (int i = 0; i < SFX_PLAYERS; i++) { for (int i = 0; i < SFX_PLAYERS; i++) {
final AudioPlayer audioPlayer = new AudioPlayerSingleStream(); final AudioPlayer audioPlayer = new AudioPlayerSingleStream(this.audioControl);
final var audioThread = Thread.ofPlatform() final var audioThread = Thread.ofPlatform()
.name("Audio SFX " + i) .name("Audio SFX " + i)
.daemon(true) .daemon(true)
@@ -91,7 +94,7 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
public void run() { public void run() {
// Music // Music
if (!this.clientEngineData.getMusicOptions().isEnable()) { if (!this.audioOptions.getMusicOptions().isEnable()) {
if (this.musicAudioPlayer.audioPlayer().isPlaying()) { if (this.musicAudioPlayer.audioPlayer().isPlaying()) {
this.musicAudioPlayer.audioPlayer().stopAudio(); this.musicAudioPlayer.audioPlayer().stopAudio();
} }
@@ -113,7 +116,7 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
} }
if (this.musicAudioPlayer.audioPlayer().isPlaying() || !this.musicPlayQueue.isEmpty()) { if (this.musicAudioPlayer.audioPlayer().isPlaying() || !this.musicPlayQueue.isEmpty()) {
this.musicAudioPlayer.audioPlayer().updateAudioOptions(this.clientEngineData.getMusicOptions()); this.musicAudioPlayer.audioPlayer().updateAudioOptions(this.audioOptions.getMusicOptions());
} }
if (!this.musicAudioPlayer.audioPlayer().isPlaying() && if (!this.musicAudioPlayer.audioPlayer().isPlaying() &&
@@ -127,32 +130,34 @@ final class AudioStrategyJavaMixer implements AudioStrategy {
// SFX // SFX
if (!this.sfxQueue.isEmpty()) { synchronized (this.sfxQueue) {
for (int i = 0; i < SFX_PLAYERS; i++) { if (!this.sfxQueue.isEmpty()) {
this.sfxAudioPlayers.get(i).audioPlayer().updateAudioOptions(this.clientEngineData.getSfxOptions()); for (int i = 0; i < SFX_PLAYERS; i++) {
this.sfxPlayersBySize.get(i).queueSize = this.sfxPlayersBySize.get(i).audioPlayer.getQueue().size(); this.sfxAudioPlayers.get(i).audioPlayer().updateAudioOptions(this.audioOptions.getSfxOptions());
} this.sfxPlayersBySize.get(i).queueSize = this.sfxPlayersBySize.get(i).audioPlayer.getQueue().size();
Collections.sort(this.sfxPlayersBySize); }
Collections.sort(this.sfxPlayersBySize);
while (!this.sfxQueue.isEmpty()) {
int i = 0; while (!this.sfxQueue.isEmpty()) {
final Iterator<StaticResource> it = this.sfxQueue.iterator(); int i = 0;
do { final Iterator<StaticResource> it = this.sfxQueue.iterator();
this.sfxPlayersBySize.get(i).queueSize++; do {
this.sfxPlayersBySize.get(i).queueSize++;
final AudioPlayer player = this.sfxPlayersBySize.get(i).audioPlayer;
final StaticResource nextFile = it.next(); final AudioPlayer player = this.sfxPlayersBySize.get(i).audioPlayer;
it.remove(); final StaticResource nextFile = it.next();
it.remove();
this.audioLoader.getQueue().offer(new AudioLoaderRequest(nextFile, true, stream -> player.getQueue().offer(new AudioData(nextFile.resourceName(), stream))));
} while (it.hasNext() && ++i < this.sfxPlayersBySize.size() && this.sfxPlayersBySize.get(i - 1).queueSize > this.sfxPlayersBySize.get(i).queueSize); this.audioLoader.getQueue().offer(new AudioLoaderRequest(nextFile, true, stream -> player.getQueue().offer(new AudioData(nextFile.resourceName(), stream))));
} while (it.hasNext() && ++i < this.sfxPlayersBySize.size() && this.sfxPlayersBySize.get(i - 1).queueSize > this.sfxPlayersBySize.get(i).queueSize);
}
} }
} }
} }
@Override @Override
public void queueSfx(final Collection<StaticResource> audioFile) { public void queueSfx(final Collection<StaticResource> audioFile) {
if (!this.clientEngineData.getSfxOptions().isEnable()) { if (!this.audioOptions.getSfxOptions().isEnable()) {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Not queued audio file (audio disabled): " + audioFile); LOGGER.debug("Not queued audio file (audio disabled): " + audioFile);
} }

View File

@@ -24,7 +24,9 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
private final static int MUSIC_INITIAL_DELAY_MS = 1000; private final static int MUSIC_INITIAL_DELAY_MS = 1000;
private final AudioOptions clientEngineData; private final AudioControl audioControl;
private final AudioOptions audioOptions;
private final AudioLoader audioLoader; private final AudioLoader audioLoader;
@@ -32,7 +34,7 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
private AudioThread audioPlayer; private AudioThread audioPlayer;
private final Set<StaticResource> sfxQueue = new LinkedHashSet<>(); private final Set<StaticResource> sfxQueue = Collections.synchronizedSet(new LinkedHashSet<>());
private final List<StaticResource> musicFiles = new ArrayList<>(); private final List<StaticResource> musicFiles = new ArrayList<>();
@@ -40,14 +42,15 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
private long musicFirstQueued; private long musicFirstQueued;
public AudioStrategySoftwareMixing(final AudioOptions clientEngineData, final AudioLoader audioLoader) { public AudioStrategySoftwareMixing(final AudioControl audioControl, final AudioOptions audioOptions, final AudioLoader audioLoader) {
this.clientEngineData = clientEngineData; this.audioControl = audioControl;
this.audioOptions = audioOptions;
this.audioLoader = audioLoader; this.audioLoader = audioLoader;
} }
@Override @Override
public void start() { public void start() {
final var multiStreamPlayer = new AudioPlayerMultiStream(); final var multiStreamPlayer = new AudioPlayerMultiStream(this.audioControl);
final var multiStreamThread = Thread.ofPlatform() final var multiStreamThread = Thread.ofPlatform()
.name("Audio MultiStream") .name("Audio MultiStream")
.daemon(true) .daemon(true)
@@ -64,13 +67,13 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
@Override @Override
public void run() { public void run() {
if (this.audioPlayer.audioPlayer().isPlaying() || !this.sfxQueue.isEmpty() || !this.musicPlayQueue.isEmpty() || !this.musicFiles.isEmpty()) { if (this.audioPlayer.audioPlayer().isPlaying() || !this.sfxQueue.isEmpty() || !this.musicPlayQueue.isEmpty() || !this.musicFiles.isEmpty()) {
this.audioPlayer.audioPlayer().updateAudioOptions(this.clientEngineData.getSfxOptions()); this.audioPlayer.audioPlayer().updateAudioOptions(this.audioOptions.getSfxOptions());
this.audioPlayer.audioPlayer().updateMusicAudioOptions(this.clientEngineData.getMusicOptions()); this.audioPlayer.audioPlayer().updateMusicAudioOptions(this.audioOptions.getMusicOptions());
} }
// Music // Music
if (!this.clientEngineData.getMusicOptions().isEnable()) { if (!this.audioOptions.getMusicOptions().isEnable()) {
if (this.audioPlayer.audioPlayer().isMusicPlaying()) { if (this.audioPlayer.audioPlayer().isMusicPlaying()) {
this.audioPlayer.audioPlayer().setStopMusic(true); this.audioPlayer.audioPlayer().setStopMusic(true);
} }
@@ -94,13 +97,15 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
// Sfx // Sfx
if (!this.sfxQueue.isEmpty()) { synchronized (this.sfxQueue) {
final Iterator<StaticResource> it = this.sfxQueue.iterator(); if (!this.sfxQueue.isEmpty()) {
while (it.hasNext()) { final Iterator<StaticResource> it = this.sfxQueue.iterator();
final StaticResource nextFile = it.next(); while (it.hasNext()) {
this.audioLoader.getQueue().offer(new AudioLoaderRequest(nextFile, true, stream -> this.audioPlayer.audioPlayer().getQueue().offer(new AudioData(nextFile.resourceName(), stream)))); final StaticResource nextFile = it.next();
this.audioLoader.getQueue().offer(new AudioLoaderRequest(nextFile, true, stream -> this.audioPlayer.audioPlayer().getQueue().offer(new AudioData(nextFile.resourceName(), stream))));
it.remove();
}
} }
this.sfxQueue.clear();
} }
} }
@@ -111,7 +116,7 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
@Override @Override
public void queueSfx(final Collection<StaticResource> audioFiles) { public void queueSfx(final Collection<StaticResource> audioFiles) {
if (!this.clientEngineData.getSfxOptions().isEnable()) { if (!this.audioOptions.getSfxOptions().isEnable()) {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Not queued audio file (audio disabled): " + audioFiles); LOGGER.debug("Not queued audio file (audio disabled): " + audioFiles);
} }
@@ -141,7 +146,7 @@ final class AudioStrategySoftwareMixing implements AudioStrategy {
} }
} }
if (musicAudioFiles.equals(this.musicFiles)) { if (musicAudioFiles.equals(this.musicFiles)) {
this.musicFirstQueued = System.currentTimeMillis(); this.musicFirstQueued = System.currentTimeMillis();
} }