*/
package org.openhab.binding.pulseaudio.internal;
+import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
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
* @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)
*/
private AudioInputStream getPCMStream(InputStream resetableInnerInputStream)
throws UnsupportedAudioFileException, IOException, UnsupportedAudioFormatException {
-
if (AudioFormat.MP3.isCompatible(audioFormat)) {
MpegAudioFileReader mpegAudioFileReader = new MpegAudioFileReader();
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);
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;
- }
- }
}
} 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());
} 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());
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) {
} 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;
}
@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");
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;
modules = new ArrayList<Module>(Parser.parseModules(listModules()));
List<AbstractAudioDeviceConfig> newItems = new ArrayList<>(); // prepare new list before assigning it
- newItems.clear();
if (configuration.sink) {
logger.debug("reading sinks");
newItems.addAll(Parser.parseSinks(listSinks(), this));
return null;
}
- /**
- * retrieves a {@link SinkInput} by its name
- *
- * @return the corresponding {@link SinkInput} to the given <code>name</code>
- */
- 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 <code>id</code>
- */
- 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 <code>name</code>
- */
- 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
*
return null;
}
- /**
- * retrieves a {@link SourceOutput} by its name
- *
- * @return the corresponding {@link SourceOutput} to the given <code>name</code>
- */
- 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 <code>id</code>
- */
- 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
*
return null;
}
+ /**
+ * Get all items previously parsed from the pulseaudio server.
+ *
+ * @return All items parsed from the pulseaudio server
+ */
public List<AbstractAudioDeviceConfig> getItems() {
return items;
}
.map(portS -> Integer.parseInt(portS));
}
- private Optional<@NonNull String> extractArgumentFromLine(String argumentWanted, String argumentLine) {
+ private Optional<String> 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);
}
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));
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;
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;
* @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);
}
@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);
}
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;
}
}
@Override
- protected ThingHandler createHandler(Thing thing) {
-
+ protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
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;
}
}
}
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) {
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;
*
* @author Tobias Bräutigam - Initial contribution
*/
+@NonNullByDefault
public class Parser {
private static final Logger LOGGER = LoggerFactory.getLogger(Parser.class);
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);
}
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);
* @param raw
* @return
*/
- private static int getNumberValue(String raw) {
+ private static int getNumberValue(@Nullable String raw) {
int id = -1;
if (raw == null) {
return 0;
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;
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;
*
* @author Tobias Bräutigam - Initial contribution
*/
+@NonNullByDefault
public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService implements DeviceStatusListener {
private final Logger logger = LoggerFactory.getLogger(PulseaudioDeviceDiscoveryService.class);
}
@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;
}
}
- @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
+ public Set<String> getAlreadyConfiguredThings() {
+ return pulseaudioBridgeHandler.getThing().getThings().stream().map(Thing::getConfiguration)
+ .map(conf -> (String) conf.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME))
+ .collect(Collectors.toSet());
}
@Override
- public void onDeviceRemoved(PulseaudioBridgeHandler bridge, AbstractAudioDeviceConfig device) {
- // this can be ignored here
+ protected void startScan() {
+ pulseaudioBridgeHandler.resetKnownActiveDevices();
}
}
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;
* @author Tobias Bräutigam - Initial contribution
*/
@Component
+@NonNullByDefault
public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(PulseaudioDiscoveryParticipant.class);
}
@Override
- public DiscoveryResult createResult(ServiceInfo info) {
+ public @Nullable DiscoveryResult createResult(ServiceInfo info) {
DiscoveryResult result = null;
ThingUID uid = getThingUID(info);
if (uid != null) {
}
@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;
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
*/
@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);
}
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;
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;
* 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);
public int refreshInterval = 30000;
+ @Nullable
private PulseaudioClient client;
private PulseAudioBindingConfiguration configuration;
private List<DeviceStatusListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
- private HashSet<String> lastActiveDevices = new HashSet<>();
+ private Set<String> lastActiveDevices = new HashSet<>();
+ @Nullable
private ScheduledFuture<?> pollingJob;
- private synchronized void update() {
+ private Set<PulseaudioHandler> 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,
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);
}
@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
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 {
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);
}
@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);
}
}
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;
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;
* @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<ThingTypeUID> 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;
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) {
} finally {
audioSink.scheduleDisconnect();
}
- @SuppressWarnings("unchecked")
- ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) 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<AudioSink> reg = (ServiceRegistration<AudioSink>) 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<AudioSink> 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) {
} finally {
audioSource.scheduleDisconnect();
}
- @SuppressWarnings("unchecked")
- ServiceRegistration<AudioSource> reg = (ServiceRegistration<AudioSource>) 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<AudioSource> reg = (ServiceRegistration<AudioSource>) 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<AudioSink> 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<AudioSource> sourceReg = audioSourceRegistrations.remove(getThing().getUID().toString());
}
@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
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;
*
* @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) {
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);
}
}
}
/**
- * 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");
}
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;
- }
}
*/
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 {
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;
}
*/
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
*
* @author Tobias Bräutigam - Initial contribution
*/
+@NonNullByDefault
public abstract class AbstractDeviceConfig {
protected int id;
*/
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
*
* @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;
}
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
*
* @author Tobias Bräutigam - Initial contribution
*/
+@NonNullByDefault
public class Sink extends AbstractAudioDeviceConfig {
protected List<String> combinedSinkNames;
protected List<Sink> 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<>();
this.combinedSinks = combinedSinks;
}
- public void addCombinedSink(Sink sink) {
+ public void addCombinedSink(@Nullable Sink sink) {
if (sink != null) {
this.combinedSinks.add(sink);
}
*/
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;
}
}
*/
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;
}
}
*/
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;
}
}