Added new enhanced sound fragment, which brings mp3 support on top of ESH. (#63)

Remove the old audio action as all features should now be covered through ESH itself.

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2016-09-30 23:00:30 +02:00
committed by GitHub
parent 8a7f5561b1
commit 7087ea3582
18 changed files with 220 additions and 771 deletions
@@ -3,7 +3,6 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="lib" path="lib/jl1.0.1.jar"/>
<classpathentry exported="true" kind="lib" path="lib/jackson-core-asl-1.9.2.jar"/>
<classpathentry exported="true" kind="lib" path="lib/jackson-mapper-asl-1.9.2.jar"/>
<classpathentry kind="output" path="target/classes"/>
@@ -43,7 +43,6 @@ Import-Package: com.google.common.base,
org.eclipse.smarthome.ui.chart,
org.eclipse.smarthome.ui.items,
org.eclipse.xtext.xbase,
org.openhab.io.multimedia.actions,
org.openhab.library.tel.types,
org.osgi.framework,
org.osgi.service.cm,
@@ -90,7 +89,6 @@ Export-Package: org.codehaus.jackson,
org.openhab.core.transform.actions,
org.openhab.core.types,
org.openhab.io.console,
org.openhab.io.multimedia.actions,
org.openhab.io.multimedia.tts,
org.openhab.io.net.actions,
org.openhab.io.net.exec,
@@ -101,8 +99,7 @@ Export-Package: org.codehaus.jackson,
org.openhab.model.sitemap,
org.openhab.ui.chart,
org.openhab.ui.items
Bundle-ClassPath: lib/jl1.0.1.jar,
.,
Bundle-ClassPath: .,
lib/jackson-core-asl-1.9.2.jar,
lib/jackson-mapper-asl-1.9.2.jar
Service-Component: OSGI-INF/*.xml
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2015-2016 by the respective copyright holders.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
-->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="false" name="org.openhab.action.audio">
<implementation class="org.openhab.io.multimedia.actions.AudioActionService"/>
<reference bind="setVoiceManager" cardinality="1..1" interface="org.eclipse.smarthome.core.voice.VoiceManager" name="VoiceManager" policy="static" unbind="unsetVoiceManager"/>
<service>
<provide interface="org.eclipse.smarthome.model.script.engine.action.ActionService"/>
</service>
</scr:component>
@@ -2,7 +2,5 @@ output.. = target/classes/
bin.includes = META-INF/,\
.,\
lib/,\
OSGI-INF/,\
lib/jackson-core-asl-1.9.2.jar,\
lib/jackson-mapper-asl-1.9.2.jar
OSGI-INF/
source.. = src/main/java/
@@ -11,7 +11,6 @@ package org.openhab.core.compat1x.internal;
import org.eclipse.smarthome.model.script.engine.ScriptEngine;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.ItemRegistry;
import org.openhab.io.multimedia.actions.Audio;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
@@ -59,7 +58,6 @@ public class CompatibilityActivator implements BundleActivator {
itemRegistryTracker.close();
eventPublisherTracker.close();
scriptEngineTracker.close();
Audio.playStream(null);
}
}
@@ -1,330 +0,0 @@
/**
* Copyright (c) 2015-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.io.console;
import java.util.Collection;
import org.apache.commons.lang.ArrayUtils;
import org.eclipse.smarthome.model.script.engine.Script;
import org.eclipse.smarthome.model.script.engine.ScriptEngine;
import org.eclipse.smarthome.model.script.engine.ScriptExecutionException;
import org.eclipse.smarthome.model.script.engine.ScriptParsingException;
import org.openhab.core.compat1x.internal.CompatibilityActivator;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemNotUniqueException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
import org.openhab.io.multimedia.actions.Audio;
import com.google.common.base.Joiner;
/**
* This class provides generic methods for handling console input (i.e. pure strings).
*
* @author Kai Kreuzer
* @since 0.4.0
*
*/
public class ConsoleInterpreter {
/**
* This method simply takes a list of arguments, where the first one is treated
* as the console command (such as "update", "send" etc.). The following entries
* are then the arguments for this command.
* If the command is unknown, the complete usage is printed to the console.
*
* @param args array which contains the console command and all its arguments
* @param console the console for printing messages for the user
*/
static public void handleRequest(String[] args, Console console) {
String arg = args[0];
args = (String[]) ArrayUtils.remove(args, 0);
if (arg.equals("items")) {
ConsoleInterpreter.handleItems(args, console);
} else if (arg.equals("send")) {
ConsoleInterpreter.handleSend(args, console);
} else if (arg.equals("update")) {
ConsoleInterpreter.handleUpdate(args, console);
} else if (arg.equals("status")) {
ConsoleInterpreter.handleStatus(args, console);
} else if (arg.equals("say")) {
ConsoleInterpreter.handleSay(args, console);
} else if (arg.equals(">")) {
ConsoleInterpreter.handleScript(args, console);
} else {
console.printUsage(getUsage());
}
}
/**
* This method handles an update command.
*
* @param args array which contains the arguments for the update command
* @param console the console for printing messages for the user
*/
static public void handleUpdate(String[] args, Console console) {
ItemRegistry registry = CompatibilityActivator.itemRegistryTracker.getService();
EventPublisher publisher = CompatibilityActivator.eventPublisherTracker.getService();
if (publisher != null) {
if (registry != null) {
if (args.length > 0) {
String itemName = args[0];
try {
Item item = registry.getItemByPattern(itemName);
if (args.length > 1) {
String stateName = args[1];
State state = TypeParser.parseState(item.getAcceptedDataTypes(), stateName);
if (state != null) {
publisher.postUpdate(item.getName(), state);
console.println("Update has been sent successfully.");
} else {
console.println(
"Error: State '" + stateName + "' is not valid for item '" + itemName + "'");
console.print("Valid data types are: ( ");
for (Class<? extends State> acceptedType : item.getAcceptedDataTypes()) {
console.print(acceptedType.getSimpleName() + " ");
}
console.println(")");
}
} else {
console.printUsage(ConsoleInterpreter.getUpdateUsage());
}
} catch (ItemNotFoundException e) {
console.println("Error: Item '" + itemName + "' does not exist.");
} catch (ItemNotUniqueException e) {
console.print("Error: Multiple items match this pattern: ");
for (Item item : e.getMatchingItems()) {
console.print(item.getName() + " ");
}
}
} else {
console.printUsage(ConsoleInterpreter.getUpdateUsage());
}
} else {
console.println("Sorry, no item registry service available!");
}
} else {
console.println("Sorry, no event publisher service available!");
}
}
/**
* This method handles a send command.
*
* @param args array which contains the arguments for the send command
* @param console the console for printing messages for the user
*/
static public void handleSend(String[] args, Console console) {
ItemRegistry registry = CompatibilityActivator.itemRegistryTracker.getService();
EventPublisher publisher = CompatibilityActivator.eventPublisherTracker.getService();
if (publisher != null) {
if (registry != null) {
if (args.length > 0) {
String itemName = args[0];
try {
Item item = registry.getItemByPattern(itemName);
if (args.length > 1) {
String commandName = args[1];
Command command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), commandName);
if (command != null) {
publisher.sendCommand(itemName, command);
console.println("Command has been sent successfully.");
} else {
console.println("Error: Command '" + commandName + "' is not valid for item '"
+ itemName + "'");
console.print("Valid command types are: ( ");
for (Class<? extends Command> acceptedType : item.getAcceptedCommandTypes()) {
console.print(acceptedType.getSimpleName() + " ");
}
console.println(")");
}
} else {
console.printUsage(ConsoleInterpreter.getCommandUsage());
}
} catch (ItemNotFoundException e) {
console.println("Error: Item '" + itemName + "' does not exist.");
} catch (ItemNotUniqueException e) {
console.print("Error: Multiple items match this pattern: ");
for (Item item : e.getMatchingItems()) {
console.print(item.getName() + " ");
}
}
} else {
console.printUsage(ConsoleInterpreter.getCommandUsage());
}
} else {
console.println("Sorry, no item registry service available!");
}
} else {
console.println("Sorry, no event publisher service available!");
}
}
/**
* This method handles an items command.
*
* @param args array which contains the arguments for the items command
* @param console the console for printing messages for the user
*/
static public void handleItems(String[] args, Console console) {
ItemRegistry registry = CompatibilityActivator.itemRegistryTracker.getService();
if (registry != null) {
String pattern = (args.length == 0) ? "*" : args[0];
Collection<Item> items = registry.getItems(pattern);
if (items.size() > 0) {
for (Item item : items) {
console.println(item.toString());
}
} else {
console.println("No items found for this pattern.");
}
} else {
console.println("Sorry, no item registry service available!");
}
}
/**
* This method handles a status command.
*
* @param args array which contains the arguments for the status command
* @param console the console for printing messages for the user
*/
static public void handleStatus(String[] args, Console console) {
ItemRegistry registry = CompatibilityActivator.itemRegistryTracker.getService();
if (registry != null) {
if (args.length > 0) {
String itemName = args[0];
try {
Item item = registry.getItemByPattern(itemName);
console.println(item.getState().toString());
} catch (ItemNotFoundException e) {
console.println("Error: Item '" + itemName + "' does not exist.");
} catch (ItemNotUniqueException e) {
console.print("Error: Multiple items match this pattern: ");
for (Item item : e.getMatchingItems()) {
console.print(item.getName() + " ");
}
}
} else {
console.printUsage(ConsoleInterpreter.getStatusUsage());
}
} else {
console.println("Sorry, no item registry service available!");
}
}
/**
* This method handles a say command.
*
* @param args array which contains the arguments for the status command
* @param console the console for printing messages for the user
*/
static public void handleSay(String[] args, Console console) {
StringBuilder msg = new StringBuilder();
for (String word : args) {
if (word.startsWith("%") && word.endsWith("%") && word.length() > 2) {
String itemName = word.substring(1, word.length() - 1);
ItemRegistry registry = CompatibilityActivator.itemRegistryTracker.getService();
if (registry != null) {
try {
Item item = registry.getItemByPattern(itemName);
msg.append(item.getState().toString());
} catch (ItemNotFoundException e) {
console.println("Error: Item '" + itemName + "' does not exist.");
} catch (ItemNotUniqueException e) {
console.print("Error: Multiple items match this pattern: ");
for (Item item : e.getMatchingItems()) {
console.print(item.getName() + " ");
}
}
} else {
console.println("Sorry, no item registry service available!");
}
} else {
msg.append(word);
}
msg.append(" ");
}
try {
Audio.say(msg.toString());
console.println("Said: " + msg);
} catch (NoClassDefFoundError e) {
// The dependency to the Audio class is optional, so we have to handle the case that it is not there
console.println("Could not perform command as no TTS service is available.");
}
}
public static void handleScript(String[] args, Console console) {
ScriptEngine scriptEngine = CompatibilityActivator.scriptEngineTracker.getService();
if (scriptEngine != null) {
String scriptString = Joiner.on(" ").join(args);
Script script;
try {
script = scriptEngine.newScriptFromString(scriptString);
Object result = script.execute();
if (result != null) {
console.println(result.toString());
} else {
console.println("OK");
}
} catch (ScriptParsingException e) {
console.println(e.getMessage());
} catch (ScriptExecutionException e) {
console.println(e.getMessage());
}
} else {
console.println("Script engine is not available.");
}
}
/** returns a CR-separated list of usage texts for all available commands */
private static String getUsage() {
StringBuilder sb = new StringBuilder();
for (String usage : ConsoleInterpreter.getUsages()) {
sb.append(usage + "\n");
}
return sb.toString();
}
/** returns an array of the usage texts for all available commands */
static public String[] getUsages() {
return new String[] { getUpdateUsage(), getCommandUsage(), getStatusUsage(), getItemsUsage(), getSayUsage(),
getScriptUsage() };
}
static public String getUpdateUsage() {
return "update <item> <state> - sends a status update for an item";
}
static public String getCommandUsage() {
return "send <item> <command> - sends a command for an item";
}
static public String getStatusUsage() {
return "status <item> - shows the current status of an item";
}
static public String getItemsUsage() {
return "items [<pattern>] - lists names and types of all items matching the pattern";
}
public static String getSayUsage() {
return "say <sentence to say> - Says a message through TTS on the host machine";
}
public static String getScriptUsage() {
return "> <script to execute> - Executes a script";
}
}
@@ -1,378 +0,0 @@
/**
* Copyright (c) 2015-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.io.multimedia.actions;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Port;
import javax.sound.sampled.UnsupportedAudioFileException;
import org.apache.commons.collections.Closure;
import org.apache.commons.io.IOUtils;
import org.eclipse.smarthome.config.core.ConfigConstants;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.scriptengine.action.ActionDoc;
import org.openhab.core.scriptengine.action.ParamDoc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;
public class Audio {
private static final String SOUND_DIR = "sounds";
private static final Logger logger = LoggerFactory.getLogger(Audio.class);
private static final Pattern plsStreamPattern = Pattern.compile("^File[0-9]=(.+)$");
private static Float macVolumeValue = null;
private static Player streamPlayer = null;
private static Socket shoutCastSocket = null;
@ActionDoc(text = "plays a sound from the sounds folder")
static public void playSound(@ParamDoc(name = "filename", text = "the filename with extension") String filename) {
try {
InputStream is = new FileInputStream(
ConfigConstants.getConfigFolder() + File.separator + SOUND_DIR + File.separator + filename);
if (filename.toLowerCase().endsWith(".mp3")) {
Player player = new Player(is);
playInThread(player);
} else {
AudioInputStream ais = AudioSystem.getAudioInputStream(is);
Clip clip = AudioSystem.getClip();
clip.open(ais);
playInThread(clip);
}
} catch (FileNotFoundException e) {
logger.error("Cannot play sound '{}': {}", new Object[] { filename, e.getMessage() });
} catch (JavaLayerException e) {
logger.error("Cannot play sound '{}': {}", new Object[] { filename, e.getMessage() });
} catch (UnsupportedAudioFileException e) {
logger.error("Format of sound file '{}' is not supported: {}", new Object[] { filename, e.getMessage() });
} catch (IOException e) {
logger.error("Cannot play sound '{}': {}", new Object[] { filename, e.getMessage() });
} catch (LineUnavailableException e) {
logger.error("Cannot play sound '{}': {}", new Object[] { filename, e.getMessage() });
}
}
@ActionDoc(text = "plays an audio stream from an url")
static public synchronized void playStream(
@ParamDoc(name = "url", text = "the url of the audio stream") String url) {
if (streamPlayer != null) {
// if we are already playing a stream, stop it first
streamPlayer.close();
streamPlayer = null;
}
if (url == null) {
// the call was only for stopping the currently playing stream
return;
}
try {
if (url.toLowerCase().endsWith(".m3u")) {
InputStream is = new URL(url).openStream();
String urls = IOUtils.toString(is);
for (String line : urls.split("\n")) {
if (!line.isEmpty() && !line.startsWith("#")) {
url = line;
break;
}
}
} else if (url.toLowerCase().endsWith(".pls")) {
InputStream is = new URL(url).openStream();
String urls = IOUtils.toString(is);
for (String line : urls.split("\n")) {
if (!line.isEmpty() && line.startsWith("File")) {
Matcher matcher = plsStreamPattern.matcher(line);
if (matcher.find()) {
url = matcher.group(1);
break;
}
}
}
}
URL streamUrl = new URL(url);
URLConnection connection = streamUrl.openConnection();
InputStream is = null;
if (connection.getContentType().equals("unknown/unknown")) {
// Java does not parse non-standard headers used by SHOUTCast
int port = streamUrl.getPort() > 0 ? streamUrl.getPort() : 80;
// Manipulate User-Agent to receive a stream
shoutCastSocket = new Socket(streamUrl.getHost(), port);
OutputStream os = shoutCastSocket.getOutputStream();
String user_agent = "WinampMPEG/5.09";
String req = "GET / HTTP/1.0\r\nuser-agent: " + user_agent
+ "\r\nIcy-MetaData: 1\r\nConnection: keep-alive\r\n\r\n";
os.write(req.getBytes());
is = shoutCastSocket.getInputStream();
} else {
is = streamUrl.openStream();
}
if (is != null) {
Player player = new Player(is);
streamPlayer = player;
playInThread(player);
}
} catch (JavaLayerException e) {
logger.error("Cannot play stream '{}': JavaLayerException - {}", url, e.getMessage());
} catch (MalformedURLException e) {
logger.error("Cannot play stream '{}': MalformedURLException - {}", url, e.getMessage());
} catch (IOException e) {
logger.error("Cannot play stream '{}': {}", url, e);
}
}
/**
* Says the given text..
*
* <p>
* This method checks for registered TTS services. If there is a service
* available for the current OS, this will be chosen. Otherwise, it
* will pick a (the first) TTS service that is platform-independent.
* </p>
*
* @param text the text to speak
*/
@ActionDoc(text = "says a given text through the default TTS service")
static public void say(@ParamDoc(name = "text") Object text) {
AudioActionService.voiceManager.say(text.toString());
}
/**
* Text-to-speech with a given voice.
*
* <p>
* This method checks for registered TTS services. If there is a service
* available for the current OS, this will be chosen. Otherwise, it
* will pick a (the first) TTS service that is platform-independent.
* </p>
*
* @param text the text to speak
* @param voice the name of the voice to use or null, if the default voice should be used
*/
@ActionDoc(text = "says a given text through the default TTS service with a given voice")
static public void say(@ParamDoc(name = "text") Object text, @ParamDoc(name = "voice") String voice) {
AudioActionService.voiceManager.say(text.toString(), voice);
}
/**
* Text-to-speech with a given voice.
*
* <p>
* This method checks for registered TTS services. If there is a service
* available for the current OS, this will be chosen. Otherwise, it
* will pick a (the first) TTS service that is platform-independent.
* </p>
*
* @param text the text to speak
* @param voice the name of the voice to use or null, if the default voice should be used
* @param device the name of audio device to be used to play the audio or null, if the default output device should
* be used
*/
@ActionDoc(text = "says a given text through the default TTS service with a given voice")
static public void say(@ParamDoc(name = "text") Object text, @ParamDoc(name = "voice") String voice,
@ParamDoc(name = "sink") String sink) {
AudioActionService.voiceManager.say(text.toString(), voice, sink);
}
@ActionDoc(text = "sets the master volume of the host")
static public void setMasterVolume(
@ParamDoc(name = "volume", text = "volume in the range [0,1]") final float volume) throws IOException {
if (volume < 0 || volume > 1) {
throw new IllegalArgumentException("Volume value must be in the range [0,1]!");
}
if (isMacOSX()) {
macVolumeValue = volume;
setMasterVolumeMac(volume * 100f);
} else {
setMasterVolumeJavaSound(volume);
}
}
@ActionDoc(text = "sets the master volume of the host")
static public void setMasterVolume(@ParamDoc(name = "percent") final PercentType percent) throws IOException {
setMasterVolume(percent.toBigDecimal().floatValue() / 100f);
}
private static void setMasterVolumeMac(float volume) throws IOException {
Runtime.getRuntime().exec(new String[] { "osascript", "-e", "set volume output volume " + volume });
}
private static void setMasterVolumeJavaSound(final float volume) {
runVolumeCommand(new Closure() {
@Override
public void execute(Object input) {
FloatControl volumeControl = (FloatControl) input;
volumeControl.setValue(volume);
}
});
}
@ActionDoc(text = "increases the master volume of the host")
static public void increaseMasterVolume(@ParamDoc(name = "percent") final float percent) throws IOException {
if (percent <= 0 || percent > 100) {
throw new IllegalArgumentException("Percent must be in the range (0,100]!");
}
Float volume = getMasterVolume();
if (volume == 0) {
// as increasing 0 by x percent will still be 0, we have to set some initial positive value
volume = 0.001f;
}
float newVolume = volume * (1f + percent / 100f);
if (isMacOSX() && newVolume - volume < .01) {
// the getMasterVolume() only returns integers, so we have to make sure that we
// increase the volume level at least by 1%.
newVolume += .01;
}
if (newVolume > 1) {
newVolume = 1;
}
setMasterVolume(newVolume);
}
@ActionDoc(text = "decreases the master volume of the host")
static public void decreaseMasterVolume(@ParamDoc(name = "percent") final float percent) throws IOException {
if (percent <= 0 || percent > 100) {
throw new IllegalArgumentException("Percent must be in the range (0,100]!");
}
float volume = getMasterVolume();
float newVolume = volume * (1f - percent / 100f);
if (isMacOSX() && newVolume > 0 && volume - newVolume < .01) {
// the getMasterVolume() only returns integers, so we have to make sure that we
// decrease the volume level at least by 1%.
newVolume -= .01;
}
if (newVolume < 0) {
newVolume = 0;
}
setMasterVolume(newVolume);
}
@ActionDoc(text = "gets the master volume of the host", returns = "volume as a float in the range [0,1]")
static public float getMasterVolume() throws IOException {
if (isMacOSX()) {
return getMasterVolumeMac();
} else {
return getMasterVolumeJavaSound();
}
}
private static synchronized float getMasterVolumeMac() throws IOException {
// we use a cache of the value as the script execution is pretty slow
if (macVolumeValue == null) {
Process p = Runtime.getRuntime()
.exec(new String[] { "osascript", "-e", "output volume of (get volume settings)" });
String value = IOUtils.toString(p.getInputStream()).trim();
macVolumeValue = Float.valueOf(value) / 100f;
}
return macVolumeValue;
}
private static float getMasterVolumeJavaSound() throws IOException {
final Float[] volumes = new Float[1];
runVolumeCommand(new Closure() {
@Override
public void execute(Object input) {
FloatControl volumeControl = (FloatControl) input;
volumes[0] = volumeControl.getValue();
}
});
if (volumes[0] != null) {
return volumes[0];
} else {
throw new IOException("Cannot determine master volume level");
}
}
private static void runVolumeCommand(Closure closure) {
Mixer.Info[] infos = AudioSystem.getMixerInfo();
for (Mixer.Info info : infos) {
Mixer mixer = AudioSystem.getMixer(info);
if (mixer.isLineSupported(Port.Info.SPEAKER)) {
Port port;
try {
port = (Port) mixer.getLine(Port.Info.SPEAKER);
port.open();
if (port.isControlSupported(FloatControl.Type.VOLUME)) {
FloatControl volume = (FloatControl) port.getControl(FloatControl.Type.VOLUME);
closure.execute(volume);
}
port.close();
} catch (LineUnavailableException e) {
logger.error("Cannot access master volume control", e);
}
}
}
}
private static void playInThread(final Clip clip) {
// run in new thread
new Thread() {
@Override
public void run() {
try {
clip.start();
while (clip.isActive()) {
sleep(1000L);
}
clip.close();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}.start();
}
static private void playInThread(final Player player) {
// run in new thread
new Thread() {
@Override
public void run() {
try {
player.play();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
if (shoutCastSocket != null) {
try {
shoutCastSocket.close();
} catch (IOException e) {
}
}
}
}
}.start();
}
private static boolean isMacOSX() {
return System.getProperty("osgi.os").equals("macosx");
}
}
@@ -1,35 +0,0 @@
/**
* Copyright (c) 2015-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.io.multimedia.actions;
import org.eclipse.smarthome.core.voice.VoiceManager;
import org.eclipse.smarthome.model.script.engine.action.ActionService;
public class AudioActionService implements ActionService {
public static VoiceManager voiceManager;
@Override
public String getActionClassName() {
return Audio.class.getCanonicalName();
}
@Override
public Class<?> getActionClass() {
return Audio.class;
}
protected void setVoiceManager(VoiceManager voiceManager) {
AudioActionService.voiceManager = voiceManager;
}
protected void unsetVoiceManager(VoiceManager voiceManager) {
AudioActionService.voiceManager = null;
}
}
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="lib" path="lib/jl1.0.1.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.io.sound</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>bndtools.core.bndbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.pde.PluginNature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,16 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: openHAB Sound Support
Bundle-SymbolicName: org.openhab.io.sound
Bundle-Version: 2.0.0.qualifier
Import-Package: org.apache.commons.collections,
org.apache.commons.io,
org.eclipse.smarthome.core.audio,
org.eclipse.smarthome.core.common,
org.slf4j
Service-Component: OSGI-INF/*.xml
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Fragment-Host: org.eclipse.smarthome.io.javasound
Bundle-ClassPath: lib/jl1.0.1.jar,
.
Bundle-ActivationPolicy: lazy
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2014-2016 by the respective copyright holders.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
-->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipse.smarthome.io.audio.enhancedjavasound">
<implementation class="org.openhab.io.sound.internal.EnhancedJavaSoundAudioSink"/>
<service>
<provide interface="org.eclipse.smarthome.core.audio.AudioSink"/>
</service>
</scr:component>
@@ -0,0 +1,5 @@
bin.includes = META-INF/,\
.,\
OSGI-INF/,\
lib/jl1.0.1.jar
source.. = src/main/java
+15
View File
@@ -0,0 +1,15 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>eclipse-plugin</packaging>
<parent>
<groupId>org.openhab.core</groupId>
<artifactId>pom-bundles</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.io.sound</artifactId>
<name>openHAB Sound Support</name>
</project>
@@ -0,0 +1,117 @@
/**
* Copyright (c) 2014-2016 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.io.sound.internal;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.eclipse.smarthome.core.audio.AudioFormat;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.URLAudioStream;
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
import org.eclipse.smarthome.io.javasound.internal.JavaSoundAudioSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;
/**
* This is an audio sink that is registered as a service, which can play mp3 files to the hosts outputs (e.g. speaker,
* line-out).
*
* @author Karel Goderis - Initial contribution and API
* @author Kai Kreuzer - Refactored and moved to openHAB
*/
public class EnhancedJavaSoundAudioSink extends JavaSoundAudioSink {
private final Logger logger = LoggerFactory.getLogger(EnhancedJavaSoundAudioSink.class);
private static AudioFormat mp3 = new AudioFormat(AudioFormat.CONTAINER_NONE, AudioFormat.CODEC_MP3, null, null,
null, null);
private static AudioFormat wav = new AudioFormat(AudioFormat.CONTAINER_WAVE, AudioFormat.CODEC_PCM_SIGNED, null,
null, null, null);
private static HashSet<AudioFormat> supportedFormats = new HashSet<>();
static {
supportedFormats.add(wav);
supportedFormats.add(mp3);
}
private static Player streamPlayer = null;
@Override
public String getId() {
return "enhancedjavasound";
}
@Override
public String getLabel(Locale locale) {
return "System Speaker";
}
@Override
public void process(final AudioStream audioStream) throws UnsupportedAudioFormatException {
if (audioStream != null && audioStream.getFormat().getCodec() != AudioFormat.CODEC_MP3) {
// we can only deal with mp3, so delegate the rest
super.process(audioStream);
} else {
if (audioStream == null || audioStream instanceof URLAudioStream) {
// we are dealing with an infinite stream here
if (streamPlayer != null) {
// if we are already playing a stream, stop it first
streamPlayer.close();
streamPlayer = null;
}
if (audioStream == null) {
// the call was only for stopping the currently playing stream
return;
}
}
try (InputStream stream = audioStream) {
Player player = new Player(stream);
streamPlayer = player;
playInThread(player);
} catch (JavaLayerException | IOException e) {
logger.error("An exception occurred while playing audio : '{}'", e.getMessage());
}
}
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return supportedFormats;
}
static private void playInThread(final Player player) {
// run in new thread
new Thread() {
@Override
public void run() {
try {
player.play();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
player.close();
}
}
}.start();
}
protected void deactivate() {
if (streamPlayer != null) {
// stop playing streams on shutdown
streamPlayer.close();
streamPlayer = null;
}
}
}
+1
View File
@@ -23,6 +23,7 @@
<module>org.openhab.io.transport.serial</module>
<module>org.openhab.io.jetty.certificate</module>
<module>org.openhab.io.rest.docs</module>
<module>org.openhab.io.sound</module>
<module>org.openhab.ui.dashboard</module>
<module>org.openhab.ui.basicui</module>
<module>org.openhab.ui.classicui</module>
@@ -30,6 +30,7 @@
<bundle prerequisite="true">mvn:org.apache.karaf.shell/org.apache.karaf.shell.core/${karaf.version}</bundle>
<bundle prerequisite="true">mvn:org.apache.karaf.wrapper/org.apache.karaf.wrapper.core/${karaf.version}</bundle>
<bundle>mvn:org.openhab.core/org.openhab.core.karaf/${project.version}</bundle>
<bundle>mvn:org.openhab.core/org.openhab.io.sound/${project.version}</bundle>
</feature>
<feature name="openhab-runtime-certificate" description="SSL Certificate Generator" version="${project.version}">