/**
* Copyright (c) 2014-2017 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.eclipse.smarthome.core.audio.internal;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.smarthome.config.core.ConfigConstants;
import org.eclipse.smarthome.config.core.ConfigOptionProvider;
import org.eclipse.smarthome.config.core.ParameterOption;
import org.eclipse.smarthome.core.audio.AudioException;
import org.eclipse.smarthome.core.audio.AudioManager;
import org.eclipse.smarthome.core.audio.AudioSink;
import org.eclipse.smarthome.core.audio.AudioSource;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.FileAudioStream;
import org.eclipse.smarthome.core.audio.URLAudioStream;
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
import org.eclipse.smarthome.core.library.types.PercentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This service provides functionality around audio services and is the central service to be used directly by others.
*
* @author Karel Goderis - Initial contribution and API
* @author Kai Kreuzer - removed unwanted dependencies
*/
public class AudioManagerImpl implements AudioManager, ConfigOptionProvider {
// constants for the configuration properties
private static final String CONFIG_URI = "system:audio";
private static final String CONFIG_DEFAULT_SINK = "defaultSink";
private static final String CONFIG_DEFAULT_SOURCE = "defaultSource";
private final Logger logger = LoggerFactory.getLogger(AudioManager.class);
// service maps
private Map<String, AudioSource> audioSources = new ConcurrentHashMap<>();
private Map<String, AudioSink> audioSinks = new ConcurrentHashMap<>();
/**
* default settings filled through the service configuration
*/
private String defaultSource;
private String defaultSink;
protected void activate(Map<String, Object> config) {
modified(config);
}
protected void deactivate() {
}
protected void modified(Map<String, Object> config) {
if (config != null) {
this.defaultSource = config.containsKey(CONFIG_DEFAULT_SOURCE)
? config.get(CONFIG_DEFAULT_SOURCE).toString() : null;
this.defaultSink = config.containsKey(CONFIG_DEFAULT_SINK) ? config.get(CONFIG_DEFAULT_SINK).toString()
: null;
}
}
@Override
public void play(AudioStream audioStream) {
play(audioStream, null);
}
@Override
public void play(AudioStream audioStream, String sinkId) {
if (audioStream != null) {
AudioSink sink = getSink(sinkId);
if (sink != null) {
try {
sink.process(audioStream);
} catch (UnsupportedAudioFormatException e) {
logger.error("Error playing '{}': {}", audioStream.toString(), e.getMessage());
}
} else {
logger.warn("Failed playing audio stream '{}' as no audio sink was found.", audioStream.toString());
}
}
}
@Override
public void playFile(String fileName) throws AudioException {
playFile(fileName, null);
}
@Override
public void playFile(String fileName, String sink) throws AudioException {
File file = new File(
ConfigConstants.getConfigFolder() + File.separator + SOUND_DIR + File.separator + fileName);
FileAudioStream is = new FileAudioStream(file);
play(is, sink);
}
@Override
public void stream(String url) throws AudioException {
stream(url, null);
}
@Override
public void stream(String url, String sinkId) throws AudioException {
AudioStream audioStream = url != null ? new URLAudioStream(url) : null;
AudioSink sink = getSink(sinkId);
if (sink != null) {
try {
sink.process(audioStream);
} catch (UnsupportedAudioFormatException e) {
logger.error("Error playing '{}': {}", url, e.getMessage());
}
}
}
@Override
public PercentType getVolume(String sinkId) {
AudioSink sink = getSink(sinkId);
if (sink != null) {
try {
return sink.getVolume();
} catch (IOException e) {
logger.error("An exception occurred while getting the volume of sink {} : '{}'", sink.getId(),
e.getMessage());
}
}
return PercentType.ZERO;
}
@Override
public void setVolume(PercentType volume, String sinkId) {
AudioSink sink = getSink(sinkId);
if (sink != null) {
try {
sink.setVolume(volume);
} catch (IOException e) {
logger.error("An exception occurred while setting the volume of sink {} : '{}'", sink.getId(),
e.getMessage());
}
}
}
@Override
public AudioSource getSource() {
AudioSource source = null;
if (defaultSource != null) {
source = audioSources.get(defaultSource);
if (source == null) {
logger.warn("Default AudioSource service '{}' not available!", defaultSource);
}
} else if (!audioSources.isEmpty()) {
source = audioSources.values().iterator().next();
} else {
logger.debug("No AudioSource service available!");
}
return source;
}
@Override
public AudioSink getSink() {
AudioSink sink = null;
if (defaultSink != null) {
sink = audioSinks.get(defaultSink);
if (sink == null) {
logger.warn("Default AudioSink service '{}' not available!", defaultSink);
}
} else if (!audioSinks.isEmpty()) {
sink = audioSinks.values().iterator().next();
} else {
logger.debug("No AudioSink service available!");
}
return sink;
}
@Override
public Set<String> getSourceIds() {
return new HashSet<>(audioSources.keySet());
}
@Override
public Set<String> getSinkIds() {
return new HashSet<>(audioSinks.keySet());
}
@Override
public Set<String> getSourceIds(String pattern) {
String regex = pattern.replace("?", ".?").replace("*", ".*?");
Set<String> matchedSources = new HashSet<String>();
for (String aSource : audioSources.keySet()) {
if (aSource.matches(regex)) {
matchedSources.add(aSource);
}
}
return matchedSources;
}
@Override
public AudioSink getSink(String sinkId) {
AudioSink sink = null;
if (sinkId == null) {
sink = getSink();
} else {
sink = audioSinks.get(sinkId);
}
return sink;
}
@Override
public Set<String> getSinks(String pattern) {
String regex = pattern.replace("?", ".?").replace("*", ".*?");
Set<String> matchedSinks = new HashSet<String>();
for (String aSink : audioSinks.keySet()) {
if (aSink.matches(regex)) {
matchedSinks.add(aSink);
}
}
return matchedSinks;
}
@Override
public Collection<ParameterOption> getParameterOptions(URI uri, String param, Locale locale) {
if (uri.toString().equals(CONFIG_URI)) {
if (CONFIG_DEFAULT_SOURCE.equals(param)) {
List<ParameterOption> options = new ArrayList<>();
for (AudioSource source : audioSources.values()) {
ParameterOption option = new ParameterOption(source.getId(), source.getLabel(locale));
options.add(option);
}
return options;
} else if (CONFIG_DEFAULT_SINK.equals(param)) {
List<ParameterOption> options = new ArrayList<>();
for (AudioSink sink : audioSinks.values()) {
ParameterOption option = new ParameterOption(sink.getId(), sink.getLabel(locale));
options.add(option);
}
return options;
}
}
return null;
}
protected void addAudioSource(AudioSource audioSource) {
this.audioSources.put(audioSource.getId(), audioSource);
}
protected void removeAudioSource(AudioSource audioSource) {
this.audioSources.remove(audioSource.getId());
}
protected void addAudioSink(AudioSink audioSink) {
this.audioSinks.put(audioSink.getId(), audioSink);
}
protected void removeAudioSink(AudioSink audioSink) {
this.audioSinks.remove(audioSink.getId());
}
}