179 lines
5.3 KiB
Java
Raw Normal View History

/*
2017-04-06 23:48:15 +02:00
* The MIT License
2019-10-12 20:05:54 +03:00
* Copyright © 2014-2019 Ilkka Seppälä
2017-04-06 23:48:15 +02:00
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
2017-04-06 23:48:15 +02:00
package com.iluwatar.event.queue;
import java.io.File;
2017-04-06 23:48:15 +02:00
import java.io.IOException;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
2017-04-06 23:48:15 +02:00
/**
* This class implements the Event Queue pattern.
*
* @author mkuprivecz
2017-04-06 23:48:15 +02:00
*/
public class Audio {
private static final Logger LOGGER = LoggerFactory.getLogger(Audio.class);
private static final Audio INSTANCE = new Audio();
2017-04-06 23:48:15 +02:00
private static final int MAX_PENDING = 16;
private int headIndex;
private int tailIndex;
2017-04-06 23:48:15 +02:00
private volatile Thread updateThread = null;
2017-04-06 23:48:15 +02:00
private PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING];
2017-04-06 23:48:15 +02:00
// Visible only for testing purposes
Audio() {
}
public static Audio getInstance() {
return INSTANCE;
}
2017-04-06 23:48:15 +02:00
/**
* This method stops the Update Method's thread and waits till service stops.
2017-04-06 23:48:15 +02:00
*/
public synchronized void stopService() throws InterruptedException {
2017-04-06 23:48:15 +02:00
if (updateThread != null) {
updateThread.interrupt();
}
updateThread.join();
updateThread = null;
2017-04-06 23:48:15 +02:00
}
/**
* This method check the Update Method's thread is started.
*
* @return boolean
*/
public synchronized boolean isServiceRunning() {
return updateThread != null && updateThread.isAlive();
}
2017-04-06 23:48:15 +02:00
/**
* Starts the thread for the Update Method pattern if it was not started previously. Also when the
* thread is is ready initializes the indexes of the queue
2017-04-06 23:48:15 +02:00
*/
public void init() {
2017-04-06 23:48:15 +02:00
if (updateThread == null) {
updateThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
update();
2017-04-06 23:48:15 +02:00
}
});
}
2017-04-28 12:19:57 +02:00
startThread();
}
2017-04-28 12:19:57 +02:00
/**
* This is a synchronized thread starter.
2017-04-28 12:19:57 +02:00
*/
private synchronized void startThread() {
2017-04-06 23:48:15 +02:00
if (!updateThread.isAlive()) {
updateThread.start();
headIndex = 0;
tailIndex = 0;
}
}
/**
* This method adds a new audio into the queue.
*
2017-04-06 23:48:15 +02:00
* @param stream is the AudioInputStream for the method
* @param volume is the level of the audio's volume
2017-04-06 23:48:15 +02:00
*/
public void playSound(AudioInputStream stream, float volume) {
2017-04-06 23:48:15 +02:00
init();
// Walk the pending requests.
for (int i = headIndex; i != tailIndex; i = (i + 1) % MAX_PENDING) {
2017-05-15 10:40:12 +02:00
if (getPendingAudio()[i].getStream() == stream) {
2017-04-06 23:48:15 +02:00
// Use the larger of the two volumes.
2017-05-15 10:40:12 +02:00
getPendingAudio()[i].setVolume(Math.max(volume, getPendingAudio()[i].getVolume()));
2017-04-06 23:48:15 +02:00
// Don't need to enqueue.
return;
}
}
2017-05-15 10:40:12 +02:00
getPendingAudio()[tailIndex] = new PlayMessage(stream, volume);
2017-04-06 23:48:15 +02:00
tailIndex = (tailIndex + 1) % MAX_PENDING;
}
2017-04-06 23:48:15 +02:00
/**
* This method uses the Update Method pattern. It takes the audio from the queue and plays it
2017-04-06 23:48:15 +02:00
*/
private void update() {
2017-04-06 23:48:15 +02:00
// If there are no pending requests, do nothing.
if (headIndex == tailIndex) {
return;
}
Clip clip = null;
try {
2017-05-15 10:40:12 +02:00
AudioInputStream audioStream = getPendingAudio()[headIndex].getStream();
headIndex++;
2017-04-06 23:48:15 +02:00
clip = AudioSystem.getClip();
2017-05-11 21:44:07 +02:00
clip.open(audioStream);
2017-04-22 17:16:38 +02:00
clip.start();
2017-04-06 23:48:15 +02:00
} catch (LineUnavailableException e) {
LOGGER.trace("Error occoured while loading the audio: The line is unavailable", e);
2017-04-06 23:48:15 +02:00
} catch (IOException e) {
LOGGER.trace("Input/Output error while loading the audio", e);
} catch (IllegalArgumentException e) {
LOGGER.trace("The system doesn't support the sound: " + e.getMessage(), e);
2017-04-06 23:48:15 +02:00
}
}
/**
* Returns the AudioInputStream of a file.
*
* @param filePath is the path of the audio file
* @return AudioInputStream
* @throws UnsupportedAudioFileException when the audio file is not supported
* @throws IOException when the file is not readable
*/
public AudioInputStream getAudioStream(String filePath)
throws UnsupportedAudioFileException, IOException {
return AudioSystem.getAudioInputStream(new File(filePath).getAbsoluteFile());
}
/**
* Returns with the message array of the queue.
*
* @return PlayMessage[]
*/
public PlayMessage[] getPendingAudio() {
return pendingAudio;
}
2017-04-06 23:48:15 +02:00
}