diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/ConvertedInputStream.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/ConvertedInputStream.java index 5fd5a35d4..6d7c76327 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/ConvertedInputStream.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/ConvertedInputStream.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.pulseaudio.internal; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -55,14 +56,13 @@ public class ConvertedInputStream extends InputStream { public ConvertedInputStream(AudioStream innerInputStream) throws UnsupportedAudioFormatException, UnsupportedAudioFileException, IOException { - this.audioFormat = innerInputStream.getFormat(); if (innerInputStream instanceof FixedLengthAudioStream) { length = ((FixedLengthAudioStream) innerInputStream).length(); } - pcmNormalizedInputStream = getPCMStreamNormalized(getPCMStream(new ResetableInputStream(innerInputStream))); + pcmNormalizedInputStream = getPCMStreamNormalized(getPCMStream(new BufferedInputStream(innerInputStream))); } @Override @@ -108,7 +108,6 @@ public class ConvertedInputStream extends InputStream { * @return A PCM normalized stream (2 channel, 44100hz, 16 bit signed) */ private AudioInputStream getPCMStreamNormalized(AudioInputStream pcmInputStream) { - javax.sound.sampled.AudioFormat format = pcmInputStream.getFormat(); if (format.getChannels() != 2 || !format.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED) @@ -138,7 +137,6 @@ public class ConvertedInputStream extends InputStream { */ private AudioInputStream getPCMStream(InputStream resetableInnerInputStream) throws UnsupportedAudioFileException, IOException, UnsupportedAudioFormatException { - if (AudioFormat.MP3.isCompatible(audioFormat)) { MpegAudioFileReader mpegAudioFileReader = new MpegAudioFileReader(); @@ -170,7 +168,6 @@ public class ConvertedInputStream extends InputStream { sourceFormat.getChannels(), sourceFormat.getChannels() * 2, sourceFormat.getSampleRate(), false); return mpegconverter.getAudioInputStream(convertFormat, sourceAIS); - } else if (AudioFormat.WAV.isCompatible(audioFormat)) { // return the same input stream, but try to compute the duration first AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(resetableInnerInputStream); @@ -187,71 +184,4 @@ public class ConvertedInputStream extends InputStream { audioFormat); } } - - /** - * This class add reset capability (on the first bytes only) - * to an AudioStream. This is necessary for the parsing / format detection. - * - */ - public static class ResetableInputStream extends InputStream { - - private static final int BUFFER_LENGTH = 10000; - - private final InputStream originalInputStream; - - private int position = -1; - private int markPosition = -1; - private int maxPreviousPosition = -2; - - private byte[] startingBuffer = new byte[BUFFER_LENGTH + 1]; - - public ResetableInputStream(InputStream originalInputStream) { - this.originalInputStream = originalInputStream; - } - - @Override - public void close() throws IOException { - originalInputStream.close(); - } - - @Override - public int read() throws IOException { - if (position >= BUFFER_LENGTH || originalInputStream.markSupported()) { - return originalInputStream.read(); - } else { - position++; - if (position <= maxPreviousPosition) { - return Byte.toUnsignedInt(startingBuffer[position]); - } else { - int currentByte = originalInputStream.read(); - startingBuffer[position] = (byte) currentByte; - maxPreviousPosition = position; - return currentByte; - } - } - } - - @Override - public synchronized void mark(int readlimit) { - if (originalInputStream.markSupported()) { - originalInputStream.mark(readlimit); - } - markPosition = position; - } - - @Override - public boolean markSupported() { - return true; - } - - @Override - public synchronized void reset() throws IOException { - if (originalInputStream.markSupported()) { - originalInputStream.reset(); - } else if (position >= BUFFER_LENGTH) { - throw new IOException("mark/reset not supported above " + BUFFER_LENGTH + " bytes"); - } - position = markPosition; - } - } } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSink.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSink.java index 7862a6d29..8e42bb1c2 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSink.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSink.java @@ -91,7 +91,9 @@ public class PulseAudioAudioSink extends PulseaudioSimpleProtocolStream implemen } catch (IOException e) { disconnect(); // disconnect force to clear connection in case of socket not cleanly shutdown if (countAttempt == 2) { // we won't retry : log and quit - String port = clientSocket != null ? Integer.toString(clientSocket.getPort()) : "unknown"; + final Socket clientSocketLocal = clientSocket; + String port = clientSocketLocal != null ? Integer.toString(clientSocketLocal.getPort()) + : "unknown"; logger.warn( "Error while trying to send audio to pulseaudio audio sink. Cannot connect to {}:{}, error: {}", pulseaudioHandler.getHost(), port, e.getMessage()); diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSource.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSource.java index 04bc37636..5369f6001 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSource.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSource.java @@ -107,7 +107,9 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem } catch (IOException e) { disconnect(); // disconnect to force clear connection in case of socket not cleanly shutdown if (countAttempt == 2) { // we won't retry : log and quit - String port = clientSocket != null ? Integer.toString(clientSocket.getPort()) : "unknown"; + final Socket clientSocketLocal = clientSocket; + String port = clientSocketLocal != null ? Integer.toString(clientSocketLocal.getPort()) + : "unknown"; logger.warn( "Error while trying to get audio from pulseaudio audio source. Cannot connect to {}:{}, error: {}", pulseaudioHandler.getHost(), port, e.getMessage()); @@ -153,11 +155,14 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem if (pipeOutputs.contains(output)) { output.flush(); } - } catch (IOException e) { - if (e instanceof InterruptedIOException && pipeOutputs.isEmpty()) { + } catch (InterruptedIOException e) { + if (pipeOutputs.isEmpty()) { // task has been ended while writing return; } + logger.warn("InterruptedIOException while writing to from pulse source pipe: {}", + getExceptionMessage(e)); + } catch (IOException e) { logger.warn("IOException while writing to from pulse source pipe: {}", getExceptionMessage(e)); } catch (RuntimeException e) { @@ -221,7 +226,8 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem } catch (IOException | InterruptedException ignored) { } try { - return (clientSocket != null) ? clientSocket.getInputStream() : null; + var clientSocketFinal = clientSocket; + return (clientSocketFinal != null) ? clientSocketFinal.getInputStream() : null; } catch (IOException ignored) { return null; } @@ -264,11 +270,14 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem @Override public int read(byte @Nullable [] b) throws IOException { - return read(b, 0, b.length); + return read(b, 0, b == null ? 0 : b.length); } @Override public int read(byte @Nullable [] b, int off, int len) throws IOException { + if (b == null) { + throw new IOException("Buffer is null"); + } logger.trace("reading from pulseaudio stream"); if (closed) { throw new IOException("Stream is closed"); diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioClient.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioClient.java index 4f7d6dc2c..4cf99f0d4 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioClient.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioClient.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Optional; import java.util.Random; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pulseaudio.internal.cli.Parser; @@ -150,7 +149,6 @@ public class PulseaudioClient { modules = new ArrayList(Parser.parseModules(listModules())); List newItems = new ArrayList<>(); // prepare new list before assigning it - newItems.clear(); if (configuration.sink) { logger.debug("reading sinks"); newItems.addAll(Parser.parseSinks(listSinks(), this)); @@ -245,48 +243,6 @@ public class PulseaudioClient { return null; } - /** - * retrieves a {@link SinkInput} by its name - * - * @return the corresponding {@link SinkInput} to the given name - */ - public @Nullable SinkInput getSinkInput(String name) { - for (AbstractAudioDeviceConfig item : items) { - if (item.getPaName().equalsIgnoreCase(name) && item instanceof SinkInput) { - return (SinkInput) item; - } - } - return null; - } - - /** - * retrieves a {@link SinkInput} by its id - * - * @return the corresponding {@link SinkInput} to the given id - */ - public @Nullable SinkInput getSinkInput(int id) { - for (AbstractAudioDeviceConfig item : items) { - if (item.getId() == id && item instanceof SinkInput) { - return (SinkInput) item; - } - } - return null; - } - - /** - * retrieves a {@link Source} by its name - * - * @return the corresponding {@link Source} to the given name - */ - public @Nullable Source getSource(String name) { - for (AbstractAudioDeviceConfig item : items) { - if (item.getPaName().equalsIgnoreCase(name) && item instanceof Source) { - return (Source) item; - } - } - return null; - } - /** * retrieves a {@link Source} by its id * @@ -301,34 +257,6 @@ public class PulseaudioClient { return null; } - /** - * retrieves a {@link SourceOutput} by its name - * - * @return the corresponding {@link SourceOutput} to the given name - */ - public @Nullable SourceOutput getSourceOutput(String name) { - for (AbstractAudioDeviceConfig item : items) { - if (item.getPaName().equalsIgnoreCase(name) && item instanceof SourceOutput) { - return (SourceOutput) item; - } - } - return null; - } - - /** - * retrieves a {@link SourceOutput} by its id - * - * @return the corresponding {@link SourceOutput} to the given id - */ - public @Nullable SourceOutput getSourceOutput(int id) { - for (AbstractAudioDeviceConfig item : items) { - if (item.getId() == id && item instanceof SourceOutput) { - return (SourceOutput) item; - } - } - return null; - } - /** * retrieves a {@link AbstractAudioDeviceConfig} by its name * @@ -343,6 +271,11 @@ public class PulseaudioClient { return null; } + /** + * Get all items previously parsed from the pulseaudio server. + * + * @return All items parsed from the pulseaudio server + */ public List getItems() { return items; } @@ -479,16 +412,18 @@ public class PulseaudioClient { .map(portS -> Integer.parseInt(portS)); } - private Optional<@NonNull String> extractArgumentFromLine(String argumentWanted, String argumentLine) { + private Optional extractArgumentFromLine(String argumentWanted, @Nullable String argumentLine) { String argument = null; - int startPortIndex = argumentLine.indexOf(argumentWanted + "="); - if (startPortIndex != -1) { - startPortIndex = startPortIndex + argumentWanted.length() + 1; - int endPortIndex = argumentLine.indexOf(" ", startPortIndex); - if (endPortIndex == -1) { - endPortIndex = argumentLine.length(); + if (argumentLine != null) { + int startPortIndex = argumentLine.indexOf(argumentWanted + "="); + if (startPortIndex != -1) { + startPortIndex = startPortIndex + argumentWanted.length() + 1; + int endPortIndex = argumentLine.indexOf(" ", startPortIndex); + if (endPortIndex == -1) { + endPortIndex = argumentLine.length(); + } + argument = argumentLine.substring(startPortIndex, endPortIndex); } - argument = argumentLine.substring(startPortIndex, endPortIndex); } return Optional.ofNullable(argument); } @@ -552,7 +487,10 @@ public class PulseaudioClient { slaves.add(sink.getPaName()); } // 1. delete old combined-sink - sendRawCommand(CMD_UNLOAD_MODULE + " " + combinedSink.getModule().getId()); + Module lastModule = combinedSink.getModule(); + if (lastModule != null) { + sendRawCommand(CMD_UNLOAD_MODULE + " " + lastModule.getId()); + } // 2. add new combined-sink with same name and all slaves sendRawCommand(CMD_LOAD_MODULE + " " + MODULE_COMBINE_SINK + " sink_name=" + combinedSink.getPaName() + " slaves=" + String.join(",", slaves)); @@ -731,8 +669,9 @@ public class PulseaudioClient { if (clientSocket == null || clientSocket.isClosed() || !clientSocket.isConnected()) { logger.trace("Try to connect..."); try { - client = new Socket(host, port); - client.setSoTimeout(500); + var clientFinal = new Socket(host, port); + clientFinal.setSoTimeout(500); + client = clientFinal; logger.trace("connected"); } catch (UnknownHostException e) { client = null; diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioHandlerFactory.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioHandlerFactory.java index 59943dc79..eb91a1dd4 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioHandlerFactory.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioHandlerFactory.java @@ -20,6 +20,8 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pulseaudio.internal.discovery.PulseaudioDeviceDiscoveryService; import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler; import org.openhab.binding.pulseaudio.internal.handler.PulseaudioHandler; @@ -47,6 +49,7 @@ import org.slf4j.LoggerFactory; * @author Tobias Bräutigam - Initial contribution */ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.pulseaudio") +@NonNullByDefault public class PulseaudioHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(PulseaudioHandlerFactory.class); @@ -64,8 +67,8 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory { } @Override - public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID, - ThingUID bridgeUID) { + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, null); } @@ -83,11 +86,11 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory { bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); } - private ThingUID getPulseaudioDeviceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration, - ThingUID bridgeUID) { + private ThingUID getPulseaudioDeviceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, + Configuration configuration, @Nullable ThingUID bridgeUID) { if (thingUID == null) { String name = (String) configuration.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME); - return new ThingUID(thingTypeUID, name, bridgeUID.getId()); + return new ThingUID(thingTypeUID, name, bridgeUID == null ? null : bridgeUID.getId()); } return thingUID; } @@ -106,8 +109,7 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory { } @Override - protected ThingHandler createHandler(Thing thing) { - + protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioSimpleProtocolStream.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioSimpleProtocolStream.java index 276ec38f7..98c8bfb3d 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioSimpleProtocolStream.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioSimpleProtocolStream.java @@ -63,9 +63,10 @@ public abstract class PulseaudioSimpleProtocolStream { if (clientSocketLocal == null || !clientSocketLocal.isConnected() || clientSocketLocal.isClosed()) { logger.debug("Simple TCP Stream connecting"); String host = pulseaudioHandler.getHost(); - int port = pulseaudioHandler.getSimpleTcpPort(); - clientSocket = new Socket(host, port); - clientSocket.setSoTimeout(pulseaudioHandler.getBasicProtocolSOTimeout()); + int port = pulseaudioHandler.getSimpleTcpPortAndLoadModuleIfNecessary(); + var clientSocketFinal = new Socket(host, port); + clientSocketFinal.setSoTimeout(pulseaudioHandler.getBasicProtocolSOTimeout()); + clientSocket = clientSocketFinal; } } @@ -86,8 +87,9 @@ public abstract class PulseaudioSimpleProtocolStream { } public void scheduleDisconnect() { - if (scheduledDisconnection != null) { - scheduledDisconnection.cancel(true); + var scheduledDisconnectionFinal = scheduledDisconnection; + if (scheduledDisconnectionFinal != null) { + scheduledDisconnectionFinal.cancel(true); } int idleTimeout = pulseaudioHandler.getIdleTimeout(); if (idleTimeout > -1) { diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/cli/Parser.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/cli/Parser.java index 8dfb2f778..47742c179 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/cli/Parser.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/cli/Parser.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pulseaudio.internal.PulseaudioClient; import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig; import org.openhab.binding.pulseaudio.internal.items.Module; @@ -34,6 +36,7 @@ import org.slf4j.LoggerFactory; * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class Parser { private static final Logger LOGGER = LoggerFactory.getLogger(Parser.class); @@ -143,10 +146,8 @@ public class Parser { if (properties.containsKey("combine.slaves")) { // this is a combined sink, the combined sink object should be String sinkNames = properties.get("combine.slaves"); - if (sinkNames != null) { - for (String sinkName : sinkNames.replace("\"", "").split(",")) { - sink.addCombinedSinkName(sinkName); - } + for (String sinkName : sinkNames.replace("\"", "").split(",")) { + sink.addCombinedSinkName(sinkName); } combinedSinks.add(sink); } @@ -270,8 +271,8 @@ public class Parser { if (properties.containsKey("volume")) { source.setVolume(parseVolume(properties.get("volume"))); } - String monitorOf = properties.get("monitor_of"); - if (monitorOf != null) { + if (properties.containsKey("monitor_of")) { + String monitorOf = properties.get("monitor_of"); source.setMonitorOf(client.getSink(Integer.valueOf(monitorOf))); } sources.add(source); @@ -373,7 +374,7 @@ public class Parser { * @param raw * @return */ - private static int getNumberValue(String raw) { + private static int getNumberValue(@Nullable String raw) { int id = -1; if (raw == null) { return 0; diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDeviceDiscoveryService.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDeviceDiscoveryService.java index e39a56eba..b36e53472 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDeviceDiscoveryService.java @@ -15,7 +15,9 @@ package org.openhab.binding.pulseaudio.internal.discovery; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants; import org.openhab.binding.pulseaudio.internal.handler.DeviceStatusListener; import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler; @@ -28,7 +30,7 @@ import org.openhab.binding.pulseaudio.internal.items.SourceOutput; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.slf4j.Logger; @@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory; * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService implements DeviceStatusListener { private final Logger logger = LoggerFactory.getLogger(PulseaudioDeviceDiscoveryService.class); @@ -66,7 +69,11 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i } @Override - public void onDeviceAdded(Bridge bridge, AbstractAudioDeviceConfig device) { + public void onDeviceAdded(Thing bridge, AbstractAudioDeviceConfig device) { + if (getAlreadyConfiguredThings().contains(device.getPaName())) { + return; + } + String uidName = device.getPaName(); logger.debug("device {} found", device); ThingTypeUID thingType = null; @@ -97,18 +104,14 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i } } + public Set getAlreadyConfiguredThings() { + return pulseaudioBridgeHandler.getThing().getThings().stream().map(Thing::getConfiguration) + .map(conf -> (String) conf.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME)) + .collect(Collectors.toSet()); + } + @Override protected void startScan() { - // this can be ignored here as we discover via the PulseaudioClient.update() mechanism - } - - @Override - public void onDeviceStateChanged(ThingUID bridge, AbstractAudioDeviceConfig device) { - // this can be ignored here - } - - @Override - public void onDeviceRemoved(PulseaudioBridgeHandler bridge, AbstractAudioDeviceConfig device) { - // this can be ignored here + pulseaudioBridgeHandler.resetKnownActiveDevices(); } } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDiscoveryParticipant.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDiscoveryParticipant.java index e76c996fd..3d2aa3b51 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/discovery/PulseaudioDiscoveryParticipant.java @@ -20,6 +20,8 @@ import java.util.Set; import javax.jmdns.ServiceInfo; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants; import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler; import org.openhab.core.config.discovery.DiscoveryResult; @@ -38,6 +40,7 @@ import org.slf4j.LoggerFactory; * @author Tobias Bräutigam - Initial contribution */ @Component +@NonNullByDefault public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant { private final Logger logger = LoggerFactory.getLogger(PulseaudioDiscoveryParticipant.class); @@ -48,7 +51,7 @@ public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant } @Override - public DiscoveryResult createResult(ServiceInfo info) { + public @Nullable DiscoveryResult createResult(ServiceInfo info) { DiscoveryResult result = null; ThingUID uid = getThingUID(info); if (uid != null) { @@ -79,15 +82,12 @@ public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant } @Override - public ThingUID getThingUID(ServiceInfo info) { - if (info != null) { - logger.debug("ServiceInfo: {}", info); - if (info.getType() != null) { - if (info.getType().equals(getServiceType())) { - logger.trace("Discovered a pulseaudio server thing with name '{}'", info.getName()); - return new ThingUID(PulseaudioBindingConstants.BRIDGE_THING_TYPE, - info.getName().replace("@", "_AT_")); - } + public @Nullable ThingUID getThingUID(ServiceInfo info) { + logger.debug("ServiceInfo: {}", info); + if (info.getType() != null) { + if (info.getType().equals(getServiceType())) { + logger.trace("Discovered a pulseaudio server thing with name '{}'", info.getName()); + return new ThingUID(PulseaudioBindingConstants.BRIDGE_THING_TYPE, info.getName().replace("@", "_AT_")); } } return null; diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/DeviceStatusListener.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/DeviceStatusListener.java index 4df412417..bd5e23642 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/DeviceStatusListener.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/DeviceStatusListener.java @@ -14,8 +14,7 @@ package org.openhab.binding.pulseaudio.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.Thing; /** * The {@link DeviceStatusListener} is notified when a device status has changed @@ -26,28 +25,11 @@ import org.openhab.core.thing.ThingUID; */ @NonNullByDefault public interface DeviceStatusListener { - - /** - * This method is called whenever the state of the given device has changed. - * - * @param bridge The Pulseaudio bridge the changed device is connected to. - * @param device The device which received the state update. - */ - public void onDeviceStateChanged(ThingUID bridge, AbstractAudioDeviceConfig device); - - /** - * This method us called whenever a device is removed. - * - * @param bridge The Pulseaudio bridge the removed device was connected to. - * @param device The device which is removed. - */ - public void onDeviceRemoved(PulseaudioBridgeHandler bridge, AbstractAudioDeviceConfig device); - /** * This method us called whenever a device is added. * * @param bridge The Pulseaudio bridge the added device was connected to. * @param device The device which is added. */ - public void onDeviceAdded(Bridge bridge, AbstractAudioDeviceConfig device); + public void onDeviceAdded(Thing bridge, AbstractAudioDeviceConfig device); } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioBridgeHandler.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioBridgeHandler.java index 133669d74..d6e0bacaf 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioBridgeHandler.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioBridgeHandler.java @@ -24,6 +24,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfiguration; import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfigurationListener; @@ -33,10 +34,12 @@ import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig; import org.openhab.core.config.core.Configuration; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; @@ -47,8 +50,10 @@ import org.slf4j.LoggerFactory; * connects it to the framework. * * @author Tobias Bräutigam - Initial contribution + * @author Gwendal Roulleau - Rewrite for child handler notification * */ +@NonNullByDefault public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseAudioBindingConfigurationListener { private final Logger logger = LoggerFactory.getLogger(PulseaudioBridgeHandler.class); @@ -60,22 +65,22 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA public int refreshInterval = 30000; + @Nullable private PulseaudioClient client; private PulseAudioBindingConfiguration configuration; private List deviceStatusListeners = new CopyOnWriteArrayList<>(); - private HashSet lastActiveDevices = new HashSet<>(); + private Set lastActiveDevices = new HashSet<>(); + @Nullable private ScheduledFuture pollingJob; - private synchronized void update() { + private Set childHandlersInitialized = new HashSet<>(); + + public synchronized void update() { try { - client.connect(); - if (getThing().getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - logger.debug("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port); - } + getClient().connect(); } catch (IOException e) { logger.debug("{}", e.getMessage(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, @@ -84,21 +89,23 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA return; } - client.update(); - for (AbstractAudioDeviceConfig device : client.getItems()) { - if (lastActiveDevices != null && lastActiveDevices.contains(device.getPaName())) { - for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) { - try { - deviceStatusListener.onDeviceStateChanged(getThing().getUID(), device); - } catch (Exception e) { - logger.warn("An exception occurred while calling the DeviceStatusListener", e); - } - } - } else { + getClient().update(); + if (getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + logger.debug("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port); + // The framework will automatically notify the child handlers as the bridge status is changed + } else { + // browse all child handlers to update status according to the result of the query to the pulse audio server + for (PulseaudioHandler pulseaudioHandler : childHandlersInitialized) { + pulseaudioHandler.deviceUpdate(getDevice(pulseaudioHandler.getName())); + } + } + // browse query result to notify add event + for (AbstractAudioDeviceConfig device : getClient().getItems()) { + if (!lastActiveDevices.contains(device.getPaName())) { for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) { try { deviceStatusListener.onDeviceAdded(getThing(), device); - deviceStatusListener.onDeviceStateChanged(getThing().getUID(), device); } catch (Exception e) { logger.warn("An exception occurred while calling the DeviceStatusListener", e); } @@ -116,18 +123,22 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { - client.update(); + getClient().update(); } else { logger.debug("received unexpected command for pulseaudio bridge '{}'.", host); } } public @Nullable AbstractAudioDeviceConfig getDevice(String name) { - return client.getGenericAudioItem(name); + return getClient().getGenericAudioItem(name); } public PulseaudioClient getClient() { - return client; + PulseaudioClient clientFinal = client; + if (clientFinal == null) { + throw new AssertionError("PulseaudioClient is null !"); + } + return clientFinal; } @Override @@ -145,10 +156,11 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA this.refreshInterval = ((BigDecimal) conf.get(BRIDGE_PARAMETER_REFRESH_INTERVAL)).intValue(); } - if (host != null && !host.isEmpty()) { + if (!host.isBlank()) { client = new PulseaudioClient(host, port, configuration); updateStatus(ThingStatus.UNKNOWN); - if (pollingJob == null || pollingJob.isCancelled()) { + final ScheduledFuture pollingJobFinal = pollingJob; + if (pollingJobFinal == null || pollingJobFinal.isCancelled()) { pollingJob = scheduler.scheduleWithFixedDelay(this::update, 0, refreshInterval, TimeUnit.MILLISECONDS); } } else { @@ -168,16 +180,14 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA job.cancel(true); pollingJob = null; } - if (client != null) { - client.disconnect(); + var clientFinal = client; + if (clientFinal != null) { + clientFinal.disconnect(); } super.dispose(); } public boolean registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) { - if (deviceStatusListener == null) { - throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener."); - } return deviceStatusListeners.add(deviceStatusListener); } @@ -187,6 +197,33 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA @Override public void bindingConfigurationChanged() { - update(); + // If the bridge thing is not well setup, we do nothing + if (getThing().getStatus() != ThingStatus.OFFLINE + || getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) { + update(); + } + } + + public void resetKnownActiveDevices() { + // If the bridge thing is not well setup, we do nothing + if (getThing().getStatus() != ThingStatus.OFFLINE + || getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) { + lastActiveDevices = new HashSet<>(); + update(); + } + } + + @Override + public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { + if (childHandler instanceof PulseaudioHandler) { + this.childHandlersInitialized.add((PulseaudioHandler) childHandler); + } else { + logger.error("This bridge can only support PulseaudioHandler child"); + } + } + + @Override + public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { + this.childHandlersInitialized.remove(childHandler); } } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioHandler.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioHandler.java index a357b6631..b6821bd5f 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioHandler.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioHandler.java @@ -21,10 +21,9 @@ import java.util.Collections; import java.util.Hashtable; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,7 +52,6 @@ import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusInfo; import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.types.Command; @@ -73,17 +71,14 @@ import org.slf4j.LoggerFactory; * @author Miguel Álvarez - Register audio source and refactor */ @NonNullByDefault -public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusListener { +public class PulseaudioHandler extends BaseThingHandler { public static final Set SUPPORTED_THING_TYPES_UIDS = Collections .unmodifiableSet(Stream.of(SINK_THING_TYPE, COMBINED_SINK_THING_TYPE, SINK_INPUT_THING_TYPE, SOURCE_THING_TYPE, SOURCE_OUTPUT_THING_TYPE).collect(Collectors.toSet())); private final Logger logger = LoggerFactory.getLogger(PulseaudioHandler.class); - private final int refresh = 60; // refresh every minute as default - private @Nullable PulseaudioBridgeHandler bridgeHandler; - private @Nullable String name; - private @Nullable ScheduledFuture refreshJob; + private String name = ""; private @Nullable PulseAudioAudioSink audioSink; private @Nullable PulseAudioAudioSource audioSource; private @Nullable Integer savedVolume; @@ -102,37 +97,32 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL public void initialize() { Configuration config = getThing().getConfiguration(); name = (String) config.get(DEVICE_PARAMETER_NAME); + initializeWithTheBridge(); + } - updateStatus(ThingStatus.UNKNOWN); - deviceOnlineWatchdog(); - - // if it's a SINK thing, then maybe we have to activate the audio sink - if (SINK_THING_TYPE.equals(thing.getThingTypeUID())) { - // check the property to see if we it's enabled : - Boolean sinkActivated = (Boolean) thing.getConfiguration().get(DEVICE_PARAMETER_AUDIO_SINK_ACTIVATION); - if (sinkActivated != null && sinkActivated) { - audioSinkSetup(); - } - } - // if it's a SOURCE thing, then maybe we have to activate the audio source - if (SOURCE_THING_TYPE.equals(thing.getThingTypeUID())) { - // check the property to see if we it's enabled : - Boolean sourceActivated = (Boolean) thing.getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOURCE_ACTIVATION); - if (sourceActivated != null && sourceActivated) { - audioSourceSetup(); - } - } + public String getName() { + return name; } private void audioSinkSetup() { + if (audioSink != null) { + // Audio sink is already setup + return; + } + if (!SINK_THING_TYPE.equals(thing.getThingTypeUID())) { + return; + } + // check the property to see if it's enabled : + Boolean sinkActivated = (Boolean) thing.getConfiguration().get(DEVICE_PARAMETER_AUDIO_SINK_ACTIVATION); + if (sinkActivated == null || !sinkActivated.booleanValue()) { + return; + } final PulseaudioHandler thisHandler = this; + PulseAudioAudioSink audioSink = new PulseAudioAudioSink(thisHandler, scheduler); scheduler.submit(new Runnable() { @Override public void run() { - // Register the sink as an audio sink in openhab - logger.trace("Registering an audio sink for pulse audio sink thing {}", thing.getUID()); - PulseAudioAudioSink audioSink = new PulseAudioAudioSink(thisHandler, scheduler); - setAudioSink(audioSink); + PulseaudioHandler.this.audioSink = audioSink; try { audioSink.connectIfNeeded(); } catch (IOException e) { @@ -144,23 +134,49 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL } finally { audioSink.scheduleDisconnect(); } - @SuppressWarnings("unchecked") - ServiceRegistration reg = (ServiceRegistration) bundleContext - .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>()); - audioSinkRegistrations.put(thing.getUID().toString(), reg); } }); + // Register the sink as an audio sink in openhab + logger.trace("Registering an audio sink for pulse audio sink thing {}", thing.getUID()); + @SuppressWarnings("unchecked") + ServiceRegistration reg = (ServiceRegistration) bundleContext + .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>()); + audioSinkRegistrations.put(thing.getUID().toString(), reg); + } + + private void audioSinkUnsetup() { + PulseAudioAudioSink sink = audioSink; + if (sink != null) { + sink.disconnect(); + audioSink = null; + } + // Unregister the potential pulse audio sink's audio sink + ServiceRegistration sinkReg = audioSinkRegistrations.remove(getThing().getUID().toString()); + if (sinkReg != null) { + logger.trace("Unregistering the audio sync service for pulse audio sink thing {}", getThing().getUID()); + sinkReg.unregister(); + } } private void audioSourceSetup() { + if (audioSource != null) { + // Audio source is already setup + return; + } + if (!SOURCE_THING_TYPE.equals(thing.getThingTypeUID())) { + return; + } + // check the property to see if it's enabled : + Boolean sourceActivated = (Boolean) thing.getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOURCE_ACTIVATION); + if (sourceActivated == null || !sourceActivated.booleanValue()) { + return; + } final PulseaudioHandler thisHandler = this; + PulseAudioAudioSource audioSource = new PulseAudioAudioSource(thisHandler, scheduler); scheduler.submit(new Runnable() { @Override public void run() { - // Register the source as an audio source in openhab - logger.trace("Registering an audio source for pulse audio source thing {}", thing.getUID()); - PulseAudioAudioSource audioSource = new PulseAudioAudioSource(thisHandler, scheduler); - setAudioSource(audioSource); + PulseaudioHandler.this.audioSource = audioSource; try { audioSource.connectIfNeeded(); } catch (IOException e) { @@ -172,41 +188,21 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL } finally { audioSource.scheduleDisconnect(); } - @SuppressWarnings("unchecked") - ServiceRegistration reg = (ServiceRegistration) bundleContext - .registerService(AudioSource.class.getName(), audioSource, new Hashtable<>()); - audioSourceRegistrations.put(thing.getUID().toString(), reg); } }); + // Register the source as an audio source in openhab + logger.trace("Registering an audio source for pulse audio source thing {}", thing.getUID()); + @SuppressWarnings("unchecked") + ServiceRegistration reg = (ServiceRegistration) bundleContext + .registerService(AudioSource.class.getName(), audioSource, new Hashtable<>()); + audioSourceRegistrations.put(thing.getUID().toString(), reg); } - @Override - public void dispose() { - ScheduledFuture job = refreshJob; - if (job != null && !job.isCancelled()) { - job.cancel(true); - refreshJob = null; - } - PulseaudioBridgeHandler briHandler = bridgeHandler; - if (briHandler != null) { - briHandler.unregisterDeviceStatusListener(this); - bridgeHandler = null; - } - logger.trace("Thing {} {} disposed.", getThing().getUID(), name); - super.dispose(); - PulseAudioAudioSink sink = audioSink; - if (sink != null) { - sink.disconnect(); - } + private void audioSourceUnsetup() { PulseAudioAudioSource source = audioSource; if (source != null) { source.disconnect(); - } - // Unregister the potential pulse audio sink's audio sink - ServiceRegistration sinkReg = audioSinkRegistrations.remove(getThing().getUID().toString()); - if (sinkReg != null) { - logger.trace("Unregistering the audio sync service for pulse audio sink thing {}", getThing().getUID()); - sinkReg.unregister(); + audioSource = null; } // Unregister the potential pulse audio source's audio sources ServiceRegistration sourceReg = audioSourceRegistrations.remove(getThing().getUID().toString()); @@ -217,68 +213,42 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL } @Override - public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { - if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE - && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) { - // Bridge is now ONLINE, restart the refresh job to get an update of the thing status without waiting - // its next planned run - ScheduledFuture job = refreshJob; - if (job != null && !job.isCancelled()) { - job.cancel(true); - refreshJob = null; - } - deviceOnlineWatchdog(); - } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE - || bridgeStatusInfo.getStatus() == ThingStatus.UNKNOWN) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } + public void dispose() { + logger.trace("Thing {} {} disposed.", getThing().getUID(), name); + super.dispose(); + audioSinkUnsetup(); + audioSourceUnsetup(); } - private void deviceOnlineWatchdog() { - Runnable runnable = () -> { - try { - PulseaudioBridgeHandler bridgeHandler = getPulseaudioBridgeHandler(); - if (bridgeHandler != null) { - if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) { - if (bridgeHandler.getDevice(name) == null) { - updateStatus(ThingStatus.OFFLINE); - this.bridgeHandler = null; - } else { - updateStatus(ThingStatus.ONLINE); - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } - } else { - logger.debug("Bridge for pulseaudio device {} not found.", name); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } - } catch (Exception e) { - logger.debug("Exception occurred during execution: {}", e.getMessage(), e); - this.bridgeHandler = null; - } - }; + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + initializeWithTheBridge(); + } - refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, refresh, TimeUnit.SECONDS); + private void initializeWithTheBridge() { + PulseaudioBridgeHandler pulseaudioBridgeHandler = getPulseaudioBridgeHandler(); + if (pulseaudioBridgeHandler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } else if (pulseaudioBridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } else { + deviceUpdate(pulseaudioBridgeHandler.getDevice(name)); + } } private synchronized @Nullable PulseaudioBridgeHandler getPulseaudioBridgeHandler() { - if (this.bridgeHandler == null) { - Bridge bridge = getBridge(); - if (bridge == null) { - logger.debug("Required bridge not defined for device {}.", name); - return null; - } - ThingHandler handler = bridge.getHandler(); - if (handler instanceof PulseaudioBridgeHandler) { - this.bridgeHandler = (PulseaudioBridgeHandler) handler; - this.bridgeHandler.registerDeviceStatusListener(this); - } else { - logger.debug("No available bridge handler found for device {} bridge {} .", name, bridge.getUID()); - return null; - } + Bridge bridge = getBridge(); + if (bridge == null) { + logger.debug("Required bridge not defined for device {}.", name); + return null; + } + ThingHandler handler = bridge.getHandler(); + if (handler instanceof PulseaudioBridgeHandler) { + return (PulseaudioBridgeHandler) handler; + } else { + logger.debug("No available bridge handler found for device {} bridge {} .", name, bridge.getUID()); + return null; } - return this.bridgeHandler; } @Override @@ -296,8 +266,7 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL AbstractAudioDeviceConfig device = briHandler.getDevice(name); if (device == null) { logger.warn("device {} not found", name); - updateStatus(ThingStatus.OFFLINE); - bridgeHandler = null; + deviceUpdate(null); return; } else { State updateState = UnDefType.UNDEF; @@ -384,19 +353,20 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL * * @return */ - public int getLastVolume() { - if (savedVolume == null) { + public Integer getLastVolume() { + Integer savedVolumeFinal = savedVolume; + if (savedVolumeFinal == null) { PulseaudioBridgeHandler briHandler = getPulseaudioBridgeHandler(); if (briHandler != null) { // refresh to get the current volume level briHandler.getClient().update(); AbstractAudioDeviceConfig device = briHandler.getDevice(name); if (device != null) { - savedVolume = device.getVolume(); + savedVolume = savedVolumeFinal = device.getVolume(); } } } - return savedVolume == null ? 50 : savedVolume; + return savedVolumeFinal == null ? 50 : savedVolumeFinal; } public void setVolume(int volume) { @@ -415,25 +385,38 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL savedVolume = volume; } - @Override - public void onDeviceStateChanged(ThingUID bridge, AbstractAudioDeviceConfig device) { - if (device.getPaName().equals(name)) { - updateStatus(ThingStatus.ONLINE); + public void deviceUpdate(@Nullable AbstractAudioDeviceConfig device) { + if (device != null && device.getPaName().equals(name)) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); logger.debug("Updating states of {} id: {}", device, VOLUME_CHANNEL); - savedVolume = device.getVolume(); - updateState(VOLUME_CHANNEL, new PercentType(savedVolume)); - updateState(MUTE_CHANNEL, device.isMuted() ? OnOffType.ON : OnOffType.OFF); - updateState(STATE_CHANNEL, - device.getState() != null ? new StringType(device.getState().toString()) : new StringType("-")); + int actualVolume = device.getVolume(); + savedVolume = actualVolume; + updateState(VOLUME_CHANNEL, new PercentType(actualVolume)); + updateState(MUTE_CHANNEL, OnOffType.from(device.isMuted())); + org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig.State state = device.getState(); + updateState(STATE_CHANNEL, state != null ? new StringType(state.toString()) : new StringType("-")); if (device instanceof SinkInput) { - updateState(ROUTE_TO_SINK_CHANNEL, - ((SinkInput) device).getSink() != null - ? new StringType(((SinkInput) device).getSink().getPaName()) - : new StringType("-")); + updateState(ROUTE_TO_SINK_CHANNEL, new StringType( + Optional.ofNullable(((SinkInput) device).getSink()).map(Sink::getPaName).orElse("-"))); } if (device instanceof Sink && ((Sink) device).isCombinedSink()) { updateState(SLAVES_CHANNEL, new StringType(String.join(",", ((Sink) device).getCombinedSinkNames()))); } + audioSinkSetup(); + audioSourceSetup(); + } else if (device == null) { + updateState(VOLUME_CHANNEL, UnDefType.UNDEF); + updateState(MUTE_CHANNEL, UnDefType.UNDEF); + updateState(STATE_CHANNEL, UnDefType.UNDEF); + if (SINK_INPUT_THING_TYPE.equals(thing.getThingTypeUID())) { + updateState(ROUTE_TO_SINK_CHANNEL, UnDefType.UNDEF); + } + if (COMBINED_SINK_THING_TYPE.equals(thing.getThingTypeUID())) { + updateState(SLAVES_CHANNEL, UnDefType.UNDEF); + } + audioSinkUnsetup(); + audioSourceUnsetup(); + updateStatus(ThingStatus.OFFLINE); } } @@ -448,14 +431,14 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL } /** - * This method will scan the pulseaudio server to find the port on which the module/sink is listening + * This method will scan the pulseaudio server to find the port on which the module/sink/source is listening * If no module is listening, then it will command the module to load on the pulse audio server, * - * @return the port on which the pulseaudio server is listening for this sink + * @return the port on which the pulseaudio server is listening for this sink/source * @throws IOException when device info is not available * @throws InterruptedException when interrupted during the loading module wait */ - public int getSimpleTcpPort() throws IOException, InterruptedException { + public int getSimpleTcpPortAndLoadModuleIfNecessary() throws IOException, InterruptedException { var briHandler = getPulseaudioBridgeHandler(); if (briHandler == null) { throw new IOException("bridge is not ready"); @@ -515,43 +498,22 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL } public int getIdleTimeout() { + var idleTimeout = 3000; var handler = getPulseaudioBridgeHandler(); - if (handler == null) { - return 30000; + if (handler != null) { + AbstractAudioDeviceConfig device = handler.getDevice(name); + String idleTimeoutPropName = (device instanceof Source) ? DEVICE_PARAMETER_AUDIO_SOURCE_IDLE_TIMEOUT + : DEVICE_PARAMETER_AUDIO_SINK_IDLE_TIMEOUT; + var idleTimeoutB = (BigDecimal) getThing().getConfiguration().get(idleTimeoutPropName); + if (idleTimeoutB != null) { + idleTimeout = idleTimeoutB.intValue(); + } } - AbstractAudioDeviceConfig device = handler.getDevice(name); - String idleTimeoutPropName = (device instanceof Source) ? DEVICE_PARAMETER_AUDIO_SOURCE_IDLE_TIMEOUT - : DEVICE_PARAMETER_AUDIO_SINK_IDLE_TIMEOUT; - var idleTimeout = (BigDecimal) getThing().getConfiguration().get(idleTimeoutPropName); - return idleTimeout != null ? idleTimeout.intValue() : 30000; + return idleTimeout; } public int getBasicProtocolSOTimeout() { var soTimeout = (BigDecimal) getThing().getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOCKET_SO_TIMEOUT); return soTimeout != null ? soTimeout.intValue() : 500; } - - @Override - public void onDeviceRemoved(PulseaudioBridgeHandler bridge, AbstractAudioDeviceConfig device) { - if (device.getPaName().equals(name)) { - bridgeHandler.unregisterDeviceStatusListener(this); - bridgeHandler = null; - audioSink.disconnect(); - audioSink = null; - updateStatus(ThingStatus.OFFLINE); - } - } - - @Override - public void onDeviceAdded(Bridge bridge, AbstractAudioDeviceConfig device) { - logger.trace("new device discovered {} by {}", device, bridge); - } - - public void setAudioSink(PulseAudioAudioSink audioSink) { - this.audioSink = audioSink; - } - - public void setAudioSource(PulseAudioAudioSource audioSource) { - this.audioSource = audioSource; - } } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractAudioDeviceConfig.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractAudioDeviceConfig.java index ae1f0d03b..68a842f2f 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractAudioDeviceConfig.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractAudioDeviceConfig.java @@ -12,12 +12,16 @@ */ package org.openhab.binding.pulseaudio.internal.items; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * GenericAudioItems are any kind of items that deal with audio data and can be * muted or their volume can be changed. * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public abstract class AbstractAudioDeviceConfig extends AbstractDeviceConfig { public enum State { @@ -28,25 +32,21 @@ public abstract class AbstractAudioDeviceConfig extends AbstractDeviceConfig { DRAINED } - protected State state; + protected @Nullable State state; protected boolean muted; protected int volume; - protected Module module; + protected @Nullable Module module; - public AbstractAudioDeviceConfig(int id, String name, Module module) { + public AbstractAudioDeviceConfig(int id, String name, @Nullable Module module) { super(id, name); this.module = module; } - public Module getModule() { + public @Nullable Module getModule() { return module; } - public void setModule(Module module) { - this.module = module; - } - - public State getState() { + public @Nullable State getState() { return state; } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractDeviceConfig.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractDeviceConfig.java index 2539a5fbf..493293147 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractDeviceConfig.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/AbstractDeviceConfig.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.pulseaudio.internal.items; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Abstract root class for all items in an pulseaudio server. Every item in a * pulseaudio server has a name and a unique id which can be inherited by this @@ -19,6 +21,7 @@ package org.openhab.binding.pulseaudio.internal.items; * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public abstract class AbstractDeviceConfig { protected int id; diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Module.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Module.java index f46ce95d2..cbe482136 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Module.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Module.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.pulseaudio.internal.items; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * In order to add a {@link Sink} to the pulseaudio server you have to * load a corresponding module. Current Module objects are needed to @@ -19,15 +22,16 @@ package org.openhab.binding.pulseaudio.internal.items; * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class Module extends AbstractDeviceConfig { - private String argument; + private @Nullable String argument; public Module(int id, String name) { super(id, name); } - public String getArgument() { + public @Nullable String getArgument() { return argument; } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Sink.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Sink.java index ce4771bc0..0f3149827 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Sink.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Sink.java @@ -15,6 +15,9 @@ package org.openhab.binding.pulseaudio.internal.items; import java.util.ArrayList; import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * On a Pulseaudio server Sinks are the devices the audio streams are routed to * (playback devices) it can be a single item or a group of other Sinks that are @@ -22,12 +25,13 @@ import java.util.List; * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class Sink extends AbstractAudioDeviceConfig { protected List combinedSinkNames; protected List combinedSinks; - public Sink(int id, String name, Module module) { + public Sink(int id, String name, @Nullable Module module) { super(id, name, module); combinedSinkNames = new ArrayList<>(); combinedSinks = new ArrayList<>(); @@ -53,7 +57,7 @@ public class Sink extends AbstractAudioDeviceConfig { this.combinedSinks = combinedSinks; } - public void addCombinedSink(Sink sink) { + public void addCombinedSink(@Nullable Sink sink) { if (sink != null) { this.combinedSinks.add(sink); } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SinkInput.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SinkInput.java index d138cddd8..fa9cb5046 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SinkInput.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SinkInput.java @@ -12,24 +12,29 @@ */ package org.openhab.binding.pulseaudio.internal.items; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * A SinkInput is an audio stream which can be routed to a {@link Sink} * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class SinkInput extends AbstractAudioDeviceConfig { + @Nullable private Sink sink; - public SinkInput(int id, String name, Module module) { + public SinkInput(int id, String name, @Nullable Module module) { super(id, name, module); } - public Sink getSink() { + public @Nullable Sink getSink() { return sink; } - public void setSink(Sink sink) { + public void setSink(@Nullable Sink sink) { this.sink = sink; } } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Source.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Source.java index 53e4220ae..c5162b3a5 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Source.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/Source.java @@ -12,25 +12,30 @@ */ package org.openhab.binding.pulseaudio.internal.items; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * A Source is a device which is the source of an audio stream (recording * device) For example microphones or line-in jacks. * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class Source extends AbstractAudioDeviceConfig { + @Nullable protected Sink monitorOf; - public Source(int id, String name, Module module) { + public Source(int id, String name, @Nullable Module module) { super(id, name, module); } - public Sink getMonitorOf() { + public @Nullable Sink getMonitorOf() { return monitorOf; } - public void setMonitorOf(Sink sink) { + public void setMonitorOf(@Nullable Sink sink) { this.monitorOf = sink; } } diff --git a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SourceOutput.java b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SourceOutput.java index 60edda6f8..0bda38545 100644 --- a/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SourceOutput.java +++ b/bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/items/SourceOutput.java @@ -12,24 +12,29 @@ */ package org.openhab.binding.pulseaudio.internal.items; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * A SourceOutput is the audio stream which is produced by a (@link Source} * * @author Tobias Bräutigam - Initial contribution */ +@NonNullByDefault public class SourceOutput extends AbstractAudioDeviceConfig { + @Nullable private Source source; - public SourceOutput(int id, String name, Module module) { + public SourceOutput(int id, String name, @Nullable Module module) { super(id, name, module); } - public Source getSource() { + public @Nullable Source getSource() { return source; } - public void setSource(Source source) { + public void setSource(@Nullable Source source) { this.source = source; } }