Skip to content

Commit

Permalink
Merge pull request #811 from Merudo/playClip
Browse files Browse the repository at this point in the history
Add playClip & defineAudioSource functions.  Change stopStream & getStreamProperties to stopSound & getSoundProperties
  • Loading branch information
Phergus authored Oct 21, 2019
2 parents 1382d5f + b407448 commit a1d665b
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 105 deletions.
149 changes: 136 additions & 13 deletions src/main/java/net/rptools/lib/sound/SoundManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,28 @@
import javafx.application.Platform;
import javafx.scene.media.AudioClip;
import net.rptools.maptool.client.functions.MediaPlayerAdapter;
import net.rptools.maptool.client.functions.SoundFunctions;
import net.rptools.maptool.language.I18N;
import net.rptools.parser.ParserException;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
* This class stores AudioClip for system sounds and sound events. Event sounds are played through
* the playSoundEvent method.
*/
public class SoundManager {
private final Map<String, AudioClip> registeredSoundMap = new HashMap<String, AudioClip>();
private final Map<String, AudioClip> soundEventMap = new HashMap<String, AudioClip>();
private static final Map<String, AudioClip> registeredSoundMap = new HashMap<String, AudioClip>();
private static final Map<String, AudioClip> soundEventMap = new HashMap<String, AudioClip>();
private static final Map<String, AudioClip> userSounds = new HashMap<String, AudioClip>();

/**
* Loads the sound list and register the system sounds.
*
* @param configPath The path for the sound resources
* @throws IOException when configPath can't be read
*/
public void configure(String configPath) throws IOException {
public static void configure(String configPath) throws IOException {
Properties props = new Properties();
InputStream clipList = SoundManager.class.getClassLoader().getResourceAsStream(configPath);
if (clipList == null) throw new IOException();
Expand All @@ -53,7 +59,7 @@ public void configure(String configPath) throws IOException {
* @param properties the property file
*/
@SuppressWarnings("unchecked")
public void configure(Properties properties) {
public static void configure(Properties properties) {
for (Enumeration<String> e = (Enumeration<String>) properties.propertyNames();
e.hasMoreElements(); ) {
String key = e.nextElement();
Expand All @@ -62,19 +68,24 @@ public void configure(Properties properties) {
}

/**
* Register a system sound from a path. If path incorrect or null, remove sound.
* Register a system sound from a path. If path incorrect or null, remove sound. Also add define
* the sound to be used in SoundFunctions.
*
* @param name the name of the sound
* @param path the path to the sound
*/
public void registerSound(String name, String path) {
public static void registerSound(String name, String path) {
if (path != null && path.trim().length() == 0) path = null;

URL url = path != null ? getClass().getClassLoader().getResource(path) : null;
URL url = path != null ? SoundManager.class.getClassLoader().getResource(path) : null;
AudioClip clip = url != null ? new AudioClip(url.toExternalForm()) : null;

if (clip != null) registeredSoundMap.put(name, clip);
else registeredSoundMap.remove(name);
if (clip != null) {
registeredSoundMap.put(name, clip);
SoundFunctions.defineSound(name, url.toExternalForm()); // add sound with defineAudioSource
} else {
registeredSoundMap.remove(name);
}
}

/**
Expand All @@ -83,7 +94,7 @@ public void registerSound(String name, String path) {
* @param name the name of the sound
* @return the audioclip of the sound
*/
public AudioClip getRegisteredSound(String name) {
public static AudioClip getRegisteredSound(String name) {
return registeredSoundMap.get(name);
}

Expand All @@ -93,7 +104,7 @@ public AudioClip getRegisteredSound(String name) {
* @param eventId a string for the eventId
* @param clip the audio clip for the sound
*/
public void registerSoundEvent(String eventId, AudioClip clip) {
public static void registerSoundEvent(String eventId, AudioClip clip) {
soundEventMap.put(eventId, clip);
}

Expand All @@ -102,7 +113,7 @@ public void registerSoundEvent(String eventId, AudioClip clip) {
*
* @param eventId a string for the eventId
*/
public void registerSoundEvent(String eventId) {
public static void registerSoundEvent(String eventId) {
registerSoundEvent(eventId, null);
}

Expand All @@ -111,7 +122,7 @@ public void registerSoundEvent(String eventId) {
*
* @param eventId a string for the eventId
*/
public void playSoundEvent(String eventId) {
public static void playSoundEvent(String eventId) {
AudioClip clip = soundEventMap.get(eventId);
double volume = MediaPlayerAdapter.getGlobalVolume();

Expand All @@ -127,4 +138,116 @@ public void run() {
});
}
}

/**
* Start a given clip from its url string.
*
* @param strUri the String url of the clip
* @param cycleCount how many times should the clip play. -1: infinite
* @param volume the volume level of the clip (0-1)
* @return false if the file doesn't exist, true otherwise
* @throws ParserException if issue with file
*/
public static boolean playClip(String strUri, int cycleCount, double volume)
throws ParserException {
if (!userSounds.containsKey(strUri)) {
try {
if (!SoundFunctions.uriExists(strUri))
return false; // leave without error message if uri ok but no file
} catch (Exception e) {
throw new ParserException(
I18N.getText(
"macro.function.sound.illegalargument",
"playClip",
strUri,
e.getLocalizedMessage()));
}
}

// run this on the JavaFX thread
Platform.runLater(
new Runnable() {
@Override
public void run() {
AudioClip clip = userSounds.get(strUri);
if (clip == null) {
clip = new AudioClip(strUri);
userSounds.put(strUri, clip);
}
double playVolume = volume * MediaPlayerAdapter.getGlobalVolume();
if (cycleCount != 0 && playVolume > 0 && !MediaPlayerAdapter.getGlobalMute()) {
clip.setCycleCount(cycleCount);
clip.play(playVolume);
}
}
});
return true;
}

/**
* Stop a given clip from its url string.
*
* @param strUri the String url of the clip
* @param remove should the clip be disposed
*/
public static void stopClip(String strUri, boolean remove) {
Platform.runLater(
new Runnable() {
@Override
public void run() {
if (strUri.equals("*")) {
for (HashMap.Entry mapElement : userSounds.entrySet()) {
((AudioClip) mapElement.getValue()).stop();
}
if (remove) userSounds.clear();
} else {
AudioClip clip = userSounds.get(strUri);
if (clip != null) clip.stop();
if (remove) userSounds.remove(strUri);
}
}
});
}

/**
* Return the properties of a clip from its uri
*
* @param strUri the String uri of the clip
* @return JSONObject for one clip, JSONArray of JSONObjects if all clips
*/
public static Object getClipProperties(String strUri) {
JSONObject info;
if (strUri.equals("*")) {
JSONArray infoArray = new JSONArray();
for (HashMap.Entry mapElement : userSounds.entrySet()) {
info = getInfo((String) mapElement.getKey());
if (info != null) infoArray.add(info);
}
return infoArray;
} else {
info = getInfo(strUri);
if (info == null) return "";
else return info;
}
}

/**
* Return the properties of a clip
*
* @return JSONObject of the properties
*/
private static JSONObject getInfo(String strUri) {
AudioClip clip = userSounds.get(strUri);
if (clip == null) return null;
try {
JSONObject info = new JSONObject();
info.put("uri", strUri);
String status = clip.isPlaying() ? "PLAYING" : "STOPPED";
info.put("status", status);
info.put("type", "clip");
return info;
} catch (Exception e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
*/
package net.rptools.maptool.client.functions;

import java.io.File;
import java.io.IOException;
import java.net.*;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -81,7 +79,8 @@ public static boolean playStream(
throws ParserException {
final Media media;
try {
if (!uriExists(strUri)) return false; // leave without error message if uri ok but no file
if (!SoundFunctions.uriExists(strUri))
return false; // leave without error message if uri ok but no file
media = new Media(strUri);
} catch (Exception e) {
throw new ParserException(
Expand Down Expand Up @@ -236,54 +235,6 @@ private void editStream(Integer cycleCount, Double volume, Double start, Double
}
}

/**
* Return the existence status of resource from String uri
*
* @param strUri the String uri of the resource
* @return true if resource exists, false otherwise
* @throws IOException if uri is url, but url is incorrect
* @throws URISyntaxException if uri is for local file, but uri is incorrect
*/
public static boolean uriExists(String strUri) throws IOException, URISyntaxException {
return isWeb(strUri) ? urlExist(strUri) : fileExist(strUri);
}

/**
* Returns true if the uri is for a web resource, false otherwise
*
* @param strUri the String uri of the resource
* @return true if String uri is URL, false otherwise
*/
private static boolean isWeb(String strUri) {
String s = strUri.trim().toLowerCase();
return s.startsWith("http://") || s.startsWith("https://");
}

/**
* Return the existence status of web resource from String uri
*
* @param strUri the String uri of the resource
* @return true if resource exists, false otherwise
* @throws IOException if uri is incorrect
*/
private static boolean urlExist(String strUri) throws IOException {
HttpURLConnection.setFollowRedirects(false);
HttpURLConnection con = (HttpURLConnection) new URL(strUri).openConnection();
con.setRequestMethod("HEAD");
return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
}

/**
* Return the existence status of local resource from String uri
*
* @param strUri the String uri of the resource
* @return true if resource exists, false otherwise
* @throws URISyntaxException if uri is incorrect
*/
private static boolean fileExist(String strUri) throws URISyntaxException {
return new File(new URI(strUri).getPath()).exists();
}

/**
* Return the properties of a stream from its uri
*
Expand Down Expand Up @@ -339,6 +290,7 @@ private JSONObject getInfo() {
info.put("bufferTime", player.getBufferProgressTime().toSeconds());
info.put("currentCount", player.getCurrentCount());
info.put("status", player.getStatus().toString());
info.put("type", "stream");
return info;
} catch (Exception e) {
return null;
Expand Down Expand Up @@ -406,36 +358,4 @@ private void updateMute() {
public static boolean getGlobalMute() {
return globalMute;
}

/**
* Convert a string into a uri string. Spaces are replaced by %20, among other things. The string
* "*" is returned as-is
*
* @param string the string to convert
* @return the converted string
*/
public static String convertToURI(Object string) {
String strUri = string.toString().trim();
if (strUri.equals("*")) return strUri;
if (!isWeb(strUri) && !strUri.toUpperCase().startsWith("FILE")) {
strUri = "FILE:/" + strUri;
}

try {
String decodedURL = URLDecoder.decode(strUri, "UTF-8");
URL url = new URL(decodedURL);
URI uri =
new URI(
url.getProtocol(),
url.getUserInfo(),
url.getHost(),
url.getPort(),
url.getPath(),
url.getQuery(),
url.getRef());
return uri.toString();
} catch (Exception ex) {
return strUri;
}
}
}
Loading

0 comments on commit a1d665b

Please sign in to comment.