* [pulseaudio] Fix sink-input configuration and other small improvements (#11272) The binding requires a parameter to activate the parsing of sink-input entries on the pulseaudio server. This patch : - document this behaviour - fix the parsing of these parameters if a configuration file is used (the old method of casting launched a class cast exception) Other small improvements : - Force a refresh/new parsing when the configuration changes - Fix scheduled disconnection : if a sound is played during the grace period, the scheduled disconnection is postponed, not added to the last - add a possibility to never disconnect the audio sink (in order to have a lower latency when playing sound) Closes #11272 Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * Small fixes after proofreading Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> Co-authored-by: Gwendal Roulleau <gwendal.roulleau@gmail.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javazoom.spi.mpeg.sampled.convert.MpegFormatConversionProvider;
|
||||
import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader;
|
||||
@@ -66,6 +67,8 @@ public class PulseAudioAudioSink implements AudioSink {
|
||||
|
||||
private boolean isIdle = true;
|
||||
|
||||
private @Nullable ScheduledFuture<?> scheduledDisconnection;
|
||||
|
||||
static {
|
||||
SUPPORTED_FORMATS.add(AudioFormat.WAV);
|
||||
SUPPORTED_FORMATS.add(AudioFormat.MP3);
|
||||
@@ -254,8 +257,14 @@ public class PulseAudioAudioSink implements AudioSink {
|
||||
}
|
||||
|
||||
public void scheduleDisconnect() {
|
||||
logger.debug("Scheduling disconnect");
|
||||
scheduler.schedule(this::disconnect, pulseaudioHandler.getIdleTimeout(), TimeUnit.MILLISECONDS);
|
||||
if (scheduledDisconnection != null) {
|
||||
scheduledDisconnection.cancel(true);
|
||||
}
|
||||
int idleTimeout = pulseaudioHandler.getIdleTimeout();
|
||||
if (idleTimeout > -1) {
|
||||
logger.debug("Scheduling disconnect");
|
||||
scheduledDisconnection = scheduler.schedule(this::disconnect, idleTimeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pulseaudio.internal;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Contains the binding configuration
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PulseAudioBindingConfiguration {
|
||||
|
||||
public boolean sink = true;
|
||||
|
||||
public boolean source = false;
|
||||
|
||||
public boolean sinkInput = false;
|
||||
|
||||
public boolean sourceOutput = false;
|
||||
|
||||
private Set<PulseAudioBindingConfigurationListener> listeners = new HashSet<>();
|
||||
|
||||
public void addPulseAudioBindingConfigurationListener(PulseAudioBindingConfigurationListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removePulseAudioBindingConfigurationListener(PulseAudioBindingConfigurationListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void update(PulseAudioBindingConfiguration newConfiguration) {
|
||||
sink = newConfiguration.sink;
|
||||
source = newConfiguration.source;
|
||||
sinkInput = newConfiguration.sinkInput;
|
||||
sourceOutput = newConfiguration.sourceOutput;
|
||||
|
||||
listeners.forEach(PulseAudioBindingConfigurationListener::bindingConfigurationChanged);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.pulseaudio.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Interface for listening to configuration change
|
||||
*
|
||||
* @author Gwendal Roulleau - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface PulseAudioBindingConfigurationListener {
|
||||
|
||||
public void bindingConfigurationChanged();
|
||||
}
|
||||
@@ -12,9 +12,6 @@
|
||||
*/
|
||||
package org.openhab.binding.pulseaudio.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
@@ -57,13 +54,4 @@ public class PulseaudioBindingConstants {
|
||||
|
||||
public static final String MODULE_SIMPLE_PROTOCOL_TCP_NAME = "module-simple-protocol-tcp";
|
||||
public static final int MODULE_SIMPLE_PROTOCOL_TCP_DEFAULT_PORT = 4711;
|
||||
|
||||
public static final Map<String, Boolean> TYPE_FILTERS = new HashMap<>();
|
||||
|
||||
static {
|
||||
TYPE_FILTERS.put(SINK_THING_TYPE.getId(), true);
|
||||
TYPE_FILTERS.put(SINK_INPUT_THING_TYPE.getId(), false);
|
||||
TYPE_FILTERS.put(SOURCE_THING_TYPE.getId(), false);
|
||||
TYPE_FILTERS.put(SOURCE_OUTPUT_THING_TYPE.getId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,11 @@ public class PulseaudioClient {
|
||||
private List<AbstractAudioDeviceConfig> items;
|
||||
private List<Module> modules;
|
||||
|
||||
/**
|
||||
* Corresponding to the global binding configuration
|
||||
*/
|
||||
private PulseAudioBindingConfiguration configuration;
|
||||
|
||||
/**
|
||||
* corresponding name to execute actions on sink items
|
||||
*/
|
||||
@@ -119,13 +124,10 @@ public class PulseaudioClient {
|
||||
*/
|
||||
private static final String MODULE_COMBINE_SINK = "module-combine-sink";
|
||||
|
||||
public PulseaudioClient() throws IOException {
|
||||
this("localhost", 4712);
|
||||
}
|
||||
|
||||
public PulseaudioClient(String host, int port) throws IOException {
|
||||
public PulseaudioClient(String host, int port, PulseAudioBindingConfiguration configuration) throws IOException {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.configuration = configuration;
|
||||
|
||||
items = new ArrayList<>();
|
||||
modules = new ArrayList<>();
|
||||
@@ -147,19 +149,19 @@ public class PulseaudioClient {
|
||||
|
||||
List<AbstractAudioDeviceConfig> newItems = new ArrayList<>(); // prepare new list before assigning it
|
||||
newItems.clear();
|
||||
if (Optional.ofNullable(TYPE_FILTERS.get(SINK_THING_TYPE.getId())).orElse(false)) {
|
||||
if (configuration.sink) {
|
||||
logger.debug("reading sinks");
|
||||
newItems.addAll(Parser.parseSinks(listSinks(), this));
|
||||
}
|
||||
if (Optional.ofNullable(TYPE_FILTERS.get(SOURCE_THING_TYPE.getId())).orElse(false)) {
|
||||
if (configuration.source) {
|
||||
logger.debug("reading sources");
|
||||
newItems.addAll(Parser.parseSources(listSources(), this));
|
||||
}
|
||||
if (Optional.ofNullable(TYPE_FILTERS.get(SINK_INPUT_THING_TYPE.getId())).orElse(false)) {
|
||||
if (configuration.sinkInput) {
|
||||
logger.debug("reading sink-inputs");
|
||||
newItems.addAll(Parser.parseSinkInputs(listSinkInputs(), this));
|
||||
}
|
||||
if (Optional.ofNullable(TYPE_FILTERS.get(SOURCE_OUTPUT_THING_TYPE.getId())).orElse(false)) {
|
||||
if (configuration.sourceOutput) {
|
||||
logger.debug("reading source-outputs");
|
||||
newItems.addAll(Parser.parseSourceOutputs(listSourceOutputs(), this));
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
package org.openhab.binding.pulseaudio.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Dictionary;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
@@ -36,7 +34,9 @@ import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -56,6 +56,8 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Map<ThingHandler, ServiceRegistration<?>> discoveryServiceReg = new HashMap<>();
|
||||
|
||||
private PulseAudioBindingConfiguration configuration = new PulseAudioBindingConfiguration();
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
@@ -109,7 +111,7 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
PulseaudioBridgeHandler handler = new PulseaudioBridgeHandler((Bridge) thing);
|
||||
PulseaudioBridgeHandler handler = new PulseaudioBridgeHandler((Bridge) thing, configuration);
|
||||
registerDeviceDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (PulseaudioHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
@@ -119,25 +121,16 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void activate(ComponentContext componentContext) {
|
||||
// The activate component call is used to access the bindings configuration
|
||||
@Activate
|
||||
protected synchronized void activate(ComponentContext componentContext, Map<String, Object> config) {
|
||||
super.activate(componentContext);
|
||||
modified(componentContext);
|
||||
modified(config);
|
||||
}
|
||||
|
||||
protected synchronized void modified(ComponentContext componentContext) {
|
||||
Dictionary<String, ?> properties = componentContext.getProperties();
|
||||
logger.info("pulseaudio configuration update received ({})", properties);
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
Enumeration<String> e = properties.keys();
|
||||
while (e.hasMoreElements()) {
|
||||
String k = e.nextElement();
|
||||
if (PulseaudioBindingConstants.TYPE_FILTERS.containsKey(k)) {
|
||||
PulseaudioBindingConstants.TYPE_FILTERS.put(k, (boolean) properties.get(k));
|
||||
}
|
||||
logger.debug("update received {}: {}", k, properties.get(k));
|
||||
}
|
||||
@Modified
|
||||
protected void modified(Map<String, Object> config) {
|
||||
configuration.update(new Configuration(config).as(PulseAudioBindingConfiguration.class));
|
||||
logger.debug("pulseaudio configuration update received ({})", config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfiguration;
|
||||
import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfigurationListener;
|
||||
import org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants;
|
||||
import org.openhab.binding.pulseaudio.internal.PulseaudioClient;
|
||||
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
|
||||
@@ -45,7 +47,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Tobias Bräutigam - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class PulseaudioBridgeHandler extends BaseBridgeHandler {
|
||||
public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseAudioBindingConfigurationListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(PulseaudioBridgeHandler.class);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
@@ -58,11 +60,17 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private PulseaudioClient client;
|
||||
|
||||
private PulseAudioBindingConfiguration configuration;
|
||||
|
||||
private List<DeviceStatusListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
|
||||
private HashSet<String> lastActiveDevices = new HashSet<>();
|
||||
|
||||
private ScheduledFuture<?> pollingJob;
|
||||
private Runnable pollingRunnable = () -> {
|
||||
update();
|
||||
};
|
||||
|
||||
private synchronized void update() {
|
||||
client.update();
|
||||
for (AbstractAudioDeviceConfig device : client.getItems()) {
|
||||
if (lastActiveDevices != null && lastActiveDevices.contains(device.getPaName())) {
|
||||
@@ -85,10 +93,11 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PulseaudioBridgeHandler(Bridge bridge) {
|
||||
public PulseaudioBridgeHandler(Bridge bridge, PulseAudioBindingConfiguration configuration) {
|
||||
super(bridge);
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,7 +141,7 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler {
|
||||
if (host != null && !host.isEmpty()) {
|
||||
Runnable connectRunnable = () -> {
|
||||
try {
|
||||
client = new PulseaudioClient(host, port);
|
||||
client = new PulseaudioClient(host, port, configuration);
|
||||
if (client.isConnected()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
logger.info("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port);
|
||||
@@ -151,10 +160,13 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler {
|
||||
host, port);
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
|
||||
this.configuration.addPulseAudioBindingConfigurationListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.configuration.removePulseAudioBindingConfigurationListener(this);
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
}
|
||||
@@ -174,4 +186,9 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler {
|
||||
public boolean unregisterDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
|
||||
return deviceStatusListeners.remove(deviceStatusListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindingConfigurationChanged() {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +146,10 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||
refreshJob = null;
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
bridgeHandler.unregisterDeviceStatusListener(this);
|
||||
bridgeHandler = null;
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.unregisterDeviceStatusListener(this);
|
||||
bridgeHandler = null;
|
||||
}
|
||||
logger.trace("Thing {} {} disposed.", getThing().getUID(), name);
|
||||
super.dispose();
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<parameter name="simpleProtocolSinkIdleTimeout" type="integer" required="false">
|
||||
<label>Idle Timeout</label>
|
||||
<description>Timeout in ms after which the connection will be closed when no stream is running. This ensures that
|
||||
your speaker is not on all the time and the pulseaudio sink can go to idle mode.
|
||||
your speaker is not on all the time and the pulseaudio sink can go to idle mode. -1 for no disconnection.
|
||||
</description>
|
||||
<default>30000</default>
|
||||
</parameter>
|
||||
|
||||
Reference in New Issue
Block a user