[pulseaudio] Small bugfixes and rewrite (#12581)
* [pulseaudio] small fixes and rewrite - All classes are now @NonNullByDefault - all build warnings cleared - no more need for a watchdog scheduled thread for every pulseaudio device : the bridge now handles sending information to child - fix bug : exception at startup when child handler try to get information from the bridge too soon is now handled by waiting 2 seconds if necessary - fix bug : playing MP3 with high bitrate is now OK with the replacement of the ResetableInputStream by a standard BufferedInputStream that handle mark/reset method better - fix bug : ghost device listener no longer receive event after dispose - fix bug : discovery doesn't show already added thing anymore - Updating the status bridge to ONLINE only AFTER the update method is done. - Use the bridgeStatusChanged method in the childhandler to get opportunity to test if the child could go ONLINE, (and by the way initialize the audiosink and audiosource, has they also need information from the bridge) Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> Co-authored-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
f65ee6c3a8
commit
2be9a658d5
|
@ -12,6 +12,7 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal;
|
package org.openhab.binding.pulseaudio.internal;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -55,14 +56,13 @@ public class ConvertedInputStream extends InputStream {
|
||||||
|
|
||||||
public ConvertedInputStream(AudioStream innerInputStream)
|
public ConvertedInputStream(AudioStream innerInputStream)
|
||||||
throws UnsupportedAudioFormatException, UnsupportedAudioFileException, IOException {
|
throws UnsupportedAudioFormatException, UnsupportedAudioFileException, IOException {
|
||||||
|
|
||||||
this.audioFormat = innerInputStream.getFormat();
|
this.audioFormat = innerInputStream.getFormat();
|
||||||
|
|
||||||
if (innerInputStream instanceof FixedLengthAudioStream) {
|
if (innerInputStream instanceof FixedLengthAudioStream) {
|
||||||
length = ((FixedLengthAudioStream) innerInputStream).length();
|
length = ((FixedLengthAudioStream) innerInputStream).length();
|
||||||
}
|
}
|
||||||
|
|
||||||
pcmNormalizedInputStream = getPCMStreamNormalized(getPCMStream(new ResetableInputStream(innerInputStream)));
|
pcmNormalizedInputStream = getPCMStreamNormalized(getPCMStream(new BufferedInputStream(innerInputStream)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -108,7 +108,6 @@ public class ConvertedInputStream extends InputStream {
|
||||||
* @return A PCM normalized stream (2 channel, 44100hz, 16 bit signed)
|
* @return A PCM normalized stream (2 channel, 44100hz, 16 bit signed)
|
||||||
*/
|
*/
|
||||||
private AudioInputStream getPCMStreamNormalized(AudioInputStream pcmInputStream) {
|
private AudioInputStream getPCMStreamNormalized(AudioInputStream pcmInputStream) {
|
||||||
|
|
||||||
javax.sound.sampled.AudioFormat format = pcmInputStream.getFormat();
|
javax.sound.sampled.AudioFormat format = pcmInputStream.getFormat();
|
||||||
if (format.getChannels() != 2
|
if (format.getChannels() != 2
|
||||||
|| !format.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED)
|
|| !format.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED)
|
||||||
|
@ -138,7 +137,6 @@ public class ConvertedInputStream extends InputStream {
|
||||||
*/
|
*/
|
||||||
private AudioInputStream getPCMStream(InputStream resetableInnerInputStream)
|
private AudioInputStream getPCMStream(InputStream resetableInnerInputStream)
|
||||||
throws UnsupportedAudioFileException, IOException, UnsupportedAudioFormatException {
|
throws UnsupportedAudioFileException, IOException, UnsupportedAudioFormatException {
|
||||||
|
|
||||||
if (AudioFormat.MP3.isCompatible(audioFormat)) {
|
if (AudioFormat.MP3.isCompatible(audioFormat)) {
|
||||||
MpegAudioFileReader mpegAudioFileReader = new MpegAudioFileReader();
|
MpegAudioFileReader mpegAudioFileReader = new MpegAudioFileReader();
|
||||||
|
|
||||||
|
@ -170,7 +168,6 @@ public class ConvertedInputStream extends InputStream {
|
||||||
sourceFormat.getChannels(), sourceFormat.getChannels() * 2, sourceFormat.getSampleRate(), false);
|
sourceFormat.getChannels(), sourceFormat.getChannels() * 2, sourceFormat.getSampleRate(), false);
|
||||||
|
|
||||||
return mpegconverter.getAudioInputStream(convertFormat, sourceAIS);
|
return mpegconverter.getAudioInputStream(convertFormat, sourceAIS);
|
||||||
|
|
||||||
} else if (AudioFormat.WAV.isCompatible(audioFormat)) {
|
} else if (AudioFormat.WAV.isCompatible(audioFormat)) {
|
||||||
// return the same input stream, but try to compute the duration first
|
// return the same input stream, but try to compute the duration first
|
||||||
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(resetableInnerInputStream);
|
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(resetableInnerInputStream);
|
||||||
|
@ -187,71 +184,4 @@ public class ConvertedInputStream extends InputStream {
|
||||||
audioFormat);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,9 @@ public class PulseAudioAudioSink extends PulseaudioSimpleProtocolStream implemen
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
disconnect(); // disconnect force to clear connection in case of socket not cleanly shutdown
|
disconnect(); // disconnect force to clear connection in case of socket not cleanly shutdown
|
||||||
if (countAttempt == 2) { // we won't retry : log and quit
|
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(
|
logger.warn(
|
||||||
"Error while trying to send audio to pulseaudio audio sink. Cannot connect to {}:{}, error: {}",
|
"Error while trying to send audio to pulseaudio audio sink. Cannot connect to {}:{}, error: {}",
|
||||||
pulseaudioHandler.getHost(), port, e.getMessage());
|
pulseaudioHandler.getHost(), port, e.getMessage());
|
||||||
|
|
|
@ -107,7 +107,9 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
disconnect(); // disconnect to force clear connection in case of socket not cleanly shutdown
|
disconnect(); // disconnect to force clear connection in case of socket not cleanly shutdown
|
||||||
if (countAttempt == 2) { // we won't retry : log and quit
|
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(
|
logger.warn(
|
||||||
"Error while trying to get audio from pulseaudio audio source. Cannot connect to {}:{}, error: {}",
|
"Error while trying to get audio from pulseaudio audio source. Cannot connect to {}:{}, error: {}",
|
||||||
pulseaudioHandler.getHost(), port, e.getMessage());
|
pulseaudioHandler.getHost(), port, e.getMessage());
|
||||||
|
@ -153,11 +155,14 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem
|
||||||
if (pipeOutputs.contains(output)) {
|
if (pipeOutputs.contains(output)) {
|
||||||
output.flush();
|
output.flush();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (InterruptedIOException e) {
|
||||||
if (e instanceof InterruptedIOException && pipeOutputs.isEmpty()) {
|
if (pipeOutputs.isEmpty()) {
|
||||||
// task has been ended while writing
|
// task has been ended while writing
|
||||||
return;
|
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: {}",
|
logger.warn("IOException while writing to from pulse source pipe: {}",
|
||||||
getExceptionMessage(e));
|
getExceptionMessage(e));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
@ -221,7 +226,8 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem
|
||||||
} catch (IOException | InterruptedException ignored) {
|
} catch (IOException | InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return (clientSocket != null) ? clientSocket.getInputStream() : null;
|
var clientSocketFinal = clientSocket;
|
||||||
|
return (clientSocketFinal != null) ? clientSocketFinal.getInputStream() : null;
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -264,11 +270,14 @@ public class PulseAudioAudioSource extends PulseaudioSimpleProtocolStream implem
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte @Nullable [] b) throws IOException {
|
public int read(byte @Nullable [] b) throws IOException {
|
||||||
return read(b, 0, b.length);
|
return read(b, 0, b == null ? 0 : b.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte @Nullable [] b, int off, int len) throws IOException {
|
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");
|
logger.trace("reading from pulseaudio stream");
|
||||||
if (closed) {
|
if (closed) {
|
||||||
throw new IOException("Stream is closed");
|
throw new IOException("Stream is closed");
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.pulseaudio.internal.cli.Parser;
|
import org.openhab.binding.pulseaudio.internal.cli.Parser;
|
||||||
|
@ -150,7 +149,6 @@ public class PulseaudioClient {
|
||||||
modules = new ArrayList<Module>(Parser.parseModules(listModules()));
|
modules = new ArrayList<Module>(Parser.parseModules(listModules()));
|
||||||
|
|
||||||
List<AbstractAudioDeviceConfig> newItems = new ArrayList<>(); // prepare new list before assigning it
|
List<AbstractAudioDeviceConfig> newItems = new ArrayList<>(); // prepare new list before assigning it
|
||||||
newItems.clear();
|
|
||||||
if (configuration.sink) {
|
if (configuration.sink) {
|
||||||
logger.debug("reading sinks");
|
logger.debug("reading sinks");
|
||||||
newItems.addAll(Parser.parseSinks(listSinks(), this));
|
newItems.addAll(Parser.parseSinks(listSinks(), this));
|
||||||
|
@ -245,48 +243,6 @@ public class PulseaudioClient {
|
||||||
return null;
|
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
|
* retrieves a {@link Source} by its id
|
||||||
*
|
*
|
||||||
|
@ -301,34 +257,6 @@ public class PulseaudioClient {
|
||||||
return null;
|
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
|
* retrieves a {@link AbstractAudioDeviceConfig} by its name
|
||||||
*
|
*
|
||||||
|
@ -343,6 +271,11 @@ public class PulseaudioClient {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all items previously parsed from the pulseaudio server.
|
||||||
|
*
|
||||||
|
* @return All items parsed from the pulseaudio server
|
||||||
|
*/
|
||||||
public List<AbstractAudioDeviceConfig> getItems() {
|
public List<AbstractAudioDeviceConfig> getItems() {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
@ -479,16 +412,18 @@ public class PulseaudioClient {
|
||||||
.map(portS -> Integer.parseInt(portS));
|
.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;
|
String argument = null;
|
||||||
int startPortIndex = argumentLine.indexOf(argumentWanted + "=");
|
if (argumentLine != null) {
|
||||||
if (startPortIndex != -1) {
|
int startPortIndex = argumentLine.indexOf(argumentWanted + "=");
|
||||||
startPortIndex = startPortIndex + argumentWanted.length() + 1;
|
if (startPortIndex != -1) {
|
||||||
int endPortIndex = argumentLine.indexOf(" ", startPortIndex);
|
startPortIndex = startPortIndex + argumentWanted.length() + 1;
|
||||||
if (endPortIndex == -1) {
|
int endPortIndex = argumentLine.indexOf(" ", startPortIndex);
|
||||||
endPortIndex = argumentLine.length();
|
if (endPortIndex == -1) {
|
||||||
|
endPortIndex = argumentLine.length();
|
||||||
|
}
|
||||||
|
argument = argumentLine.substring(startPortIndex, endPortIndex);
|
||||||
}
|
}
|
||||||
argument = argumentLine.substring(startPortIndex, endPortIndex);
|
|
||||||
}
|
}
|
||||||
return Optional.ofNullable(argument);
|
return Optional.ofNullable(argument);
|
||||||
}
|
}
|
||||||
|
@ -552,7 +487,10 @@ public class PulseaudioClient {
|
||||||
slaves.add(sink.getPaName());
|
slaves.add(sink.getPaName());
|
||||||
}
|
}
|
||||||
// 1. delete old combined-sink
|
// 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
|
// 2. add new combined-sink with same name and all slaves
|
||||||
sendRawCommand(CMD_LOAD_MODULE + " " + MODULE_COMBINE_SINK + " sink_name=" + combinedSink.getPaName()
|
sendRawCommand(CMD_LOAD_MODULE + " " + MODULE_COMBINE_SINK + " sink_name=" + combinedSink.getPaName()
|
||||||
+ " slaves=" + String.join(",", slaves));
|
+ " slaves=" + String.join(",", slaves));
|
||||||
|
@ -731,8 +669,9 @@ public class PulseaudioClient {
|
||||||
if (clientSocket == null || clientSocket.isClosed() || !clientSocket.isConnected()) {
|
if (clientSocket == null || clientSocket.isClosed() || !clientSocket.isConnected()) {
|
||||||
logger.trace("Try to connect...");
|
logger.trace("Try to connect...");
|
||||||
try {
|
try {
|
||||||
client = new Socket(host, port);
|
var clientFinal = new Socket(host, port);
|
||||||
client.setSoTimeout(500);
|
clientFinal.setSoTimeout(500);
|
||||||
|
client = clientFinal;
|
||||||
logger.trace("connected");
|
logger.trace("connected");
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
client = null;
|
client = null;
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
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.discovery.PulseaudioDeviceDiscoveryService;
|
||||||
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler;
|
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler;
|
||||||
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioHandler;
|
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioHandler;
|
||||||
|
@ -47,6 +49,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.pulseaudio")
|
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.pulseaudio")
|
||||||
|
@NonNullByDefault
|
||||||
public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||||
private final Logger logger = LoggerFactory.getLogger(PulseaudioHandlerFactory.class);
|
private final Logger logger = LoggerFactory.getLogger(PulseaudioHandlerFactory.class);
|
||||||
|
|
||||||
|
@ -64,8 +67,8 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
|
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
|
||||||
ThingUID bridgeUID) {
|
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
|
||||||
if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||||
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
return super.createThing(thingTypeUID, configuration, thingUID, null);
|
||||||
}
|
}
|
||||||
|
@ -83,11 +86,11 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ThingUID getPulseaudioDeviceUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration,
|
private ThingUID getPulseaudioDeviceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
|
||||||
ThingUID bridgeUID) {
|
Configuration configuration, @Nullable ThingUID bridgeUID) {
|
||||||
if (thingUID == null) {
|
if (thingUID == null) {
|
||||||
String name = (String) configuration.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME);
|
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;
|
return thingUID;
|
||||||
}
|
}
|
||||||
|
@ -106,8 +109,7 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ThingHandler createHandler(Thing thing) {
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
if (PulseaudioBridgeHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||||
|
|
|
@ -63,9 +63,10 @@ public abstract class PulseaudioSimpleProtocolStream {
|
||||||
if (clientSocketLocal == null || !clientSocketLocal.isConnected() || clientSocketLocal.isClosed()) {
|
if (clientSocketLocal == null || !clientSocketLocal.isConnected() || clientSocketLocal.isClosed()) {
|
||||||
logger.debug("Simple TCP Stream connecting");
|
logger.debug("Simple TCP Stream connecting");
|
||||||
String host = pulseaudioHandler.getHost();
|
String host = pulseaudioHandler.getHost();
|
||||||
int port = pulseaudioHandler.getSimpleTcpPort();
|
int port = pulseaudioHandler.getSimpleTcpPortAndLoadModuleIfNecessary();
|
||||||
clientSocket = new Socket(host, port);
|
var clientSocketFinal = new Socket(host, port);
|
||||||
clientSocket.setSoTimeout(pulseaudioHandler.getBasicProtocolSOTimeout());
|
clientSocketFinal.setSoTimeout(pulseaudioHandler.getBasicProtocolSOTimeout());
|
||||||
|
clientSocket = clientSocketFinal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +87,9 @@ public abstract class PulseaudioSimpleProtocolStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scheduleDisconnect() {
|
public void scheduleDisconnect() {
|
||||||
if (scheduledDisconnection != null) {
|
var scheduledDisconnectionFinal = scheduledDisconnection;
|
||||||
scheduledDisconnection.cancel(true);
|
if (scheduledDisconnectionFinal != null) {
|
||||||
|
scheduledDisconnectionFinal.cancel(true);
|
||||||
}
|
}
|
||||||
int idleTimeout = pulseaudioHandler.getIdleTimeout();
|
int idleTimeout = pulseaudioHandler.getIdleTimeout();
|
||||||
if (idleTimeout > -1) {
|
if (idleTimeout > -1) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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.PulseaudioClient;
|
||||||
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
|
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
|
||||||
import org.openhab.binding.pulseaudio.internal.items.Module;
|
import org.openhab.binding.pulseaudio.internal.items.Module;
|
||||||
|
@ -34,6 +36,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class Parser {
|
public class Parser {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Parser.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(Parser.class);
|
||||||
|
|
||||||
|
@ -143,10 +146,8 @@ public class Parser {
|
||||||
if (properties.containsKey("combine.slaves")) {
|
if (properties.containsKey("combine.slaves")) {
|
||||||
// this is a combined sink, the combined sink object should be
|
// this is a combined sink, the combined sink object should be
|
||||||
String sinkNames = properties.get("combine.slaves");
|
String sinkNames = properties.get("combine.slaves");
|
||||||
if (sinkNames != null) {
|
for (String sinkName : sinkNames.replace("\"", "").split(",")) {
|
||||||
for (String sinkName : sinkNames.replace("\"", "").split(",")) {
|
sink.addCombinedSinkName(sinkName);
|
||||||
sink.addCombinedSinkName(sinkName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
combinedSinks.add(sink);
|
combinedSinks.add(sink);
|
||||||
}
|
}
|
||||||
|
@ -270,8 +271,8 @@ public class Parser {
|
||||||
if (properties.containsKey("volume")) {
|
if (properties.containsKey("volume")) {
|
||||||
source.setVolume(parseVolume(properties.get("volume")));
|
source.setVolume(parseVolume(properties.get("volume")));
|
||||||
}
|
}
|
||||||
String monitorOf = properties.get("monitor_of");
|
if (properties.containsKey("monitor_of")) {
|
||||||
if (monitorOf != null) {
|
String monitorOf = properties.get("monitor_of");
|
||||||
source.setMonitorOf(client.getSink(Integer.valueOf(monitorOf)));
|
source.setMonitorOf(client.getSink(Integer.valueOf(monitorOf)));
|
||||||
}
|
}
|
||||||
sources.add(source);
|
sources.add(source);
|
||||||
|
@ -373,7 +374,7 @@ public class Parser {
|
||||||
* @param raw
|
* @param raw
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private static int getNumberValue(String raw) {
|
private static int getNumberValue(@Nullable String raw) {
|
||||||
int id = -1;
|
int id = -1;
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -15,7 +15,9 @@ package org.openhab.binding.pulseaudio.internal.discovery;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.PulseaudioBindingConstants;
|
||||||
import org.openhab.binding.pulseaudio.internal.handler.DeviceStatusListener;
|
import org.openhab.binding.pulseaudio.internal.handler.DeviceStatusListener;
|
||||||
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler;
|
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.AbstractDiscoveryService;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
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.ThingTypeUID;
|
||||||
import org.openhab.core.thing.ThingUID;
|
import org.openhab.core.thing.ThingUID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService implements DeviceStatusListener {
|
public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService implements DeviceStatusListener {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(PulseaudioDeviceDiscoveryService.class);
|
private final Logger logger = LoggerFactory.getLogger(PulseaudioDeviceDiscoveryService.class);
|
||||||
|
@ -66,7 +69,11 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
String uidName = device.getPaName();
|
||||||
logger.debug("device {} found", device);
|
logger.debug("device {} found", device);
|
||||||
ThingTypeUID thingType = null;
|
ThingTypeUID thingType = null;
|
||||||
|
@ -97,18 +104,14 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
protected void startScan() {
|
protected void startScan() {
|
||||||
// this can be ignored here as we discover via the PulseaudioClient.update() mechanism
|
pulseaudioBridgeHandler.resetKnownActiveDevices();
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Set;
|
||||||
|
|
||||||
import javax.jmdns.ServiceInfo;
|
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.PulseaudioBindingConstants;
|
||||||
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler;
|
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
|
@ -38,6 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
|
@NonNullByDefault
|
||||||
public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(PulseaudioDiscoveryParticipant.class);
|
private final Logger logger = LoggerFactory.getLogger(PulseaudioDiscoveryParticipant.class);
|
||||||
|
@ -48,7 +51,7 @@ public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DiscoveryResult createResult(ServiceInfo info) {
|
public @Nullable DiscoveryResult createResult(ServiceInfo info) {
|
||||||
DiscoveryResult result = null;
|
DiscoveryResult result = null;
|
||||||
ThingUID uid = getThingUID(info);
|
ThingUID uid = getThingUID(info);
|
||||||
if (uid != null) {
|
if (uid != null) {
|
||||||
|
@ -79,15 +82,12 @@ public class PulseaudioDiscoveryParticipant implements MDNSDiscoveryParticipant
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThingUID getThingUID(ServiceInfo info) {
|
public @Nullable ThingUID getThingUID(ServiceInfo info) {
|
||||||
if (info != null) {
|
logger.debug("ServiceInfo: {}", info);
|
||||||
logger.debug("ServiceInfo: {}", info);
|
if (info.getType() != null) {
|
||||||
if (info.getType() != null) {
|
if (info.getType().equals(getServiceType())) {
|
||||||
if (info.getType().equals(getServiceType())) {
|
logger.trace("Discovered a pulseaudio server thing with name '{}'", info.getName());
|
||||||
logger.trace("Discovered a pulseaudio server thing with name '{}'", info.getName());
|
return new ThingUID(PulseaudioBindingConstants.BRIDGE_THING_TYPE, info.getName().replace("@", "_AT_"));
|
||||||
return new ThingUID(PulseaudioBindingConstants.BRIDGE_THING_TYPE,
|
|
||||||
info.getName().replace("@", "_AT_"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -14,8 +14,7 @@ package org.openhab.binding.pulseaudio.internal.handler;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
|
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link DeviceStatusListener} is notified when a device status has changed
|
* The {@link DeviceStatusListener} is notified when a device status has changed
|
||||||
|
@ -26,28 +25,11 @@ import org.openhab.core.thing.ThingUID;
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public interface DeviceStatusListener {
|
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.
|
* This method us called whenever a device is added.
|
||||||
*
|
*
|
||||||
* @param bridge The Pulseaudio bridge the added device was connected to.
|
* @param bridge The Pulseaudio bridge the added device was connected to.
|
||||||
* @param device The device which is added.
|
* @param device The device which is added.
|
||||||
*/
|
*/
|
||||||
public void onDeviceAdded(Bridge bridge, AbstractAudioDeviceConfig device);
|
public void onDeviceAdded(Thing bridge, AbstractAudioDeviceConfig device);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfiguration;
|
import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfiguration;
|
||||||
import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfigurationListener;
|
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.config.core.Configuration;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
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.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -47,8 +50,10 @@ import org.slf4j.LoggerFactory;
|
||||||
* connects it to the framework.
|
* connects it to the framework.
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
|
* @author Gwendal Roulleau - Rewrite for child handler notification
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseAudioBindingConfigurationListener {
|
public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseAudioBindingConfigurationListener {
|
||||||
private final Logger logger = LoggerFactory.getLogger(PulseaudioBridgeHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(PulseaudioBridgeHandler.class);
|
||||||
|
|
||||||
|
@ -60,22 +65,22 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
|
||||||
|
|
||||||
public int refreshInterval = 30000;
|
public int refreshInterval = 30000;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private PulseaudioClient client;
|
private PulseaudioClient client;
|
||||||
|
|
||||||
private PulseAudioBindingConfiguration configuration;
|
private PulseAudioBindingConfiguration configuration;
|
||||||
|
|
||||||
private List<DeviceStatusListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
|
private List<DeviceStatusListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
|
||||||
private HashSet<String> lastActiveDevices = new HashSet<>();
|
private Set<String> lastActiveDevices = new HashSet<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private ScheduledFuture<?> pollingJob;
|
private ScheduledFuture<?> pollingJob;
|
||||||
|
|
||||||
private synchronized void update() {
|
private Set<PulseaudioHandler> childHandlersInitialized = new HashSet<>();
|
||||||
|
|
||||||
|
public synchronized void update() {
|
||||||
try {
|
try {
|
||||||
client.connect();
|
getClient().connect();
|
||||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
logger.debug("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.debug("{}", e.getMessage(), e);
|
logger.debug("{}", e.getMessage(), e);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
@ -84,21 +89,23 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.update();
|
getClient().update();
|
||||||
for (AbstractAudioDeviceConfig device : client.getItems()) {
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
if (lastActiveDevices != null && lastActiveDevices.contains(device.getPaName())) {
|
updateStatus(ThingStatus.ONLINE);
|
||||||
for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) {
|
logger.debug("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port);
|
||||||
try {
|
// The framework will automatically notify the child handlers as the bridge status is changed
|
||||||
deviceStatusListener.onDeviceStateChanged(getThing().getUID(), device);
|
} else {
|
||||||
} catch (Exception e) {
|
// browse all child handlers to update status according to the result of the query to the pulse audio server
|
||||||
logger.warn("An exception occurred while calling the DeviceStatusListener", e);
|
for (PulseaudioHandler pulseaudioHandler : childHandlersInitialized) {
|
||||||
}
|
pulseaudioHandler.deviceUpdate(getDevice(pulseaudioHandler.getName()));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
// browse query result to notify add event
|
||||||
|
for (AbstractAudioDeviceConfig device : getClient().getItems()) {
|
||||||
|
if (!lastActiveDevices.contains(device.getPaName())) {
|
||||||
for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) {
|
for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) {
|
||||||
try {
|
try {
|
||||||
deviceStatusListener.onDeviceAdded(getThing(), device);
|
deviceStatusListener.onDeviceAdded(getThing(), device);
|
||||||
deviceStatusListener.onDeviceStateChanged(getThing().getUID(), device);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("An exception occurred while calling the DeviceStatusListener", e);
|
logger.warn("An exception occurred while calling the DeviceStatusListener", e);
|
||||||
}
|
}
|
||||||
|
@ -116,18 +123,22 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (command instanceof RefreshType) {
|
if (command instanceof RefreshType) {
|
||||||
client.update();
|
getClient().update();
|
||||||
} else {
|
} else {
|
||||||
logger.debug("received unexpected command for pulseaudio bridge '{}'.", host);
|
logger.debug("received unexpected command for pulseaudio bridge '{}'.", host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable AbstractAudioDeviceConfig getDevice(String name) {
|
public @Nullable AbstractAudioDeviceConfig getDevice(String name) {
|
||||||
return client.getGenericAudioItem(name);
|
return getClient().getGenericAudioItem(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PulseaudioClient getClient() {
|
public PulseaudioClient getClient() {
|
||||||
return client;
|
PulseaudioClient clientFinal = client;
|
||||||
|
if (clientFinal == null) {
|
||||||
|
throw new AssertionError("PulseaudioClient is null !");
|
||||||
|
}
|
||||||
|
return clientFinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,10 +156,11 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
|
||||||
this.refreshInterval = ((BigDecimal) conf.get(BRIDGE_PARAMETER_REFRESH_INTERVAL)).intValue();
|
this.refreshInterval = ((BigDecimal) conf.get(BRIDGE_PARAMETER_REFRESH_INTERVAL)).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host != null && !host.isEmpty()) {
|
if (!host.isBlank()) {
|
||||||
client = new PulseaudioClient(host, port, configuration);
|
client = new PulseaudioClient(host, port, configuration);
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
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);
|
pollingJob = scheduler.scheduleWithFixedDelay(this::update, 0, refreshInterval, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -168,16 +180,14 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
|
||||||
job.cancel(true);
|
job.cancel(true);
|
||||||
pollingJob = null;
|
pollingJob = null;
|
||||||
}
|
}
|
||||||
if (client != null) {
|
var clientFinal = client;
|
||||||
client.disconnect();
|
if (clientFinal != null) {
|
||||||
|
clientFinal.disconnect();
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
|
public boolean registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
|
||||||
if (deviceStatusListener == null) {
|
|
||||||
throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
|
|
||||||
}
|
|
||||||
return deviceStatusListeners.add(deviceStatusListener);
|
return deviceStatusListeners.add(deviceStatusListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +197,33 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindingConfigurationChanged() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,9 @@ import java.util.Collections;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
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.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.ThingStatusInfo;
|
import org.openhab.core.thing.ThingStatusInfo;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
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.BaseThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
@ -73,17 +71,14 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Miguel Álvarez - Register audio source and refactor
|
* @author Miguel Álvarez - Register audio source and refactor
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusListener {
|
public class PulseaudioHandler extends BaseThingHandler {
|
||||||
|
|
||||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||||
.unmodifiableSet(Stream.of(SINK_THING_TYPE, COMBINED_SINK_THING_TYPE, SINK_INPUT_THING_TYPE,
|
.unmodifiableSet(Stream.of(SINK_THING_TYPE, COMBINED_SINK_THING_TYPE, SINK_INPUT_THING_TYPE,
|
||||||
SOURCE_THING_TYPE, SOURCE_OUTPUT_THING_TYPE).collect(Collectors.toSet()));
|
SOURCE_THING_TYPE, SOURCE_OUTPUT_THING_TYPE).collect(Collectors.toSet()));
|
||||||
private final Logger logger = LoggerFactory.getLogger(PulseaudioHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(PulseaudioHandler.class);
|
||||||
private final int refresh = 60; // refresh every minute as default
|
|
||||||
|
|
||||||
private @Nullable PulseaudioBridgeHandler bridgeHandler;
|
private String name = "";
|
||||||
private @Nullable String name;
|
|
||||||
private @Nullable ScheduledFuture<?> refreshJob;
|
|
||||||
private @Nullable PulseAudioAudioSink audioSink;
|
private @Nullable PulseAudioAudioSink audioSink;
|
||||||
private @Nullable PulseAudioAudioSource audioSource;
|
private @Nullable PulseAudioAudioSource audioSource;
|
||||||
private @Nullable Integer savedVolume;
|
private @Nullable Integer savedVolume;
|
||||||
|
@ -102,37 +97,32 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
Configuration config = getThing().getConfiguration();
|
Configuration config = getThing().getConfiguration();
|
||||||
name = (String) config.get(DEVICE_PARAMETER_NAME);
|
name = (String) config.get(DEVICE_PARAMETER_NAME);
|
||||||
|
initializeWithTheBridge();
|
||||||
|
}
|
||||||
|
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
public String getName() {
|
||||||
deviceOnlineWatchdog();
|
return name;
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void audioSinkSetup() {
|
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;
|
final PulseaudioHandler thisHandler = this;
|
||||||
|
PulseAudioAudioSink audioSink = new PulseAudioAudioSink(thisHandler, scheduler);
|
||||||
scheduler.submit(new Runnable() {
|
scheduler.submit(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Register the sink as an audio sink in openhab
|
PulseaudioHandler.this.audioSink = audioSink;
|
||||||
logger.trace("Registering an audio sink for pulse audio sink thing {}", thing.getUID());
|
|
||||||
PulseAudioAudioSink audioSink = new PulseAudioAudioSink(thisHandler, scheduler);
|
|
||||||
setAudioSink(audioSink);
|
|
||||||
try {
|
try {
|
||||||
audioSink.connectIfNeeded();
|
audioSink.connectIfNeeded();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -144,23 +134,49 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
} finally {
|
} finally {
|
||||||
audioSink.scheduleDisconnect();
|
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() {
|
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;
|
final PulseaudioHandler thisHandler = this;
|
||||||
|
PulseAudioAudioSource audioSource = new PulseAudioAudioSource(thisHandler, scheduler);
|
||||||
scheduler.submit(new Runnable() {
|
scheduler.submit(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Register the source as an audio source in openhab
|
PulseaudioHandler.this.audioSource = audioSource;
|
||||||
logger.trace("Registering an audio source for pulse audio source thing {}", thing.getUID());
|
|
||||||
PulseAudioAudioSource audioSource = new PulseAudioAudioSource(thisHandler, scheduler);
|
|
||||||
setAudioSource(audioSource);
|
|
||||||
try {
|
try {
|
||||||
audioSource.connectIfNeeded();
|
audioSource.connectIfNeeded();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -172,41 +188,21 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
} finally {
|
} finally {
|
||||||
audioSource.scheduleDisconnect();
|
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
|
private void audioSourceUnsetup() {
|
||||||
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();
|
|
||||||
}
|
|
||||||
PulseAudioAudioSource source = audioSource;
|
PulseAudioAudioSource source = audioSource;
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
source.disconnect();
|
source.disconnect();
|
||||||
}
|
audioSource = 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();
|
|
||||||
}
|
}
|
||||||
// Unregister the potential pulse audio source's audio sources
|
// Unregister the potential pulse audio source's audio sources
|
||||||
ServiceRegistration<AudioSource> sourceReg = audioSourceRegistrations.remove(getThing().getUID().toString());
|
ServiceRegistration<AudioSource> sourceReg = audioSourceRegistrations.remove(getThing().getUID().toString());
|
||||||
|
@ -217,68 +213,42 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
public void dispose() {
|
||||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
|
logger.trace("Thing {} {} disposed.", getThing().getUID(), name);
|
||||||
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
super.dispose();
|
||||||
// Bridge is now ONLINE, restart the refresh job to get an update of the thing status without waiting
|
audioSinkUnsetup();
|
||||||
// its next planned run
|
audioSourceUnsetup();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deviceOnlineWatchdog() {
|
@Override
|
||||||
Runnable runnable = () -> {
|
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||||
try {
|
initializeWithTheBridge();
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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() {
|
private synchronized @Nullable PulseaudioBridgeHandler getPulseaudioBridgeHandler() {
|
||||||
if (this.bridgeHandler == null) {
|
Bridge bridge = getBridge();
|
||||||
Bridge bridge = getBridge();
|
if (bridge == null) {
|
||||||
if (bridge == null) {
|
logger.debug("Required bridge not defined for device {}.", name);
|
||||||
logger.debug("Required bridge not defined for device {}.", name);
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
ThingHandler handler = bridge.getHandler();
|
||||||
ThingHandler handler = bridge.getHandler();
|
if (handler instanceof PulseaudioBridgeHandler) {
|
||||||
if (handler instanceof PulseaudioBridgeHandler) {
|
return (PulseaudioBridgeHandler) handler;
|
||||||
this.bridgeHandler = (PulseaudioBridgeHandler) handler;
|
} else {
|
||||||
this.bridgeHandler.registerDeviceStatusListener(this);
|
logger.debug("No available bridge handler found for device {} bridge {} .", name, bridge.getUID());
|
||||||
} else {
|
return null;
|
||||||
logger.debug("No available bridge handler found for device {} bridge {} .", name, bridge.getUID());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this.bridgeHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -296,8 +266,7 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
|
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
logger.warn("device {} not found", name);
|
logger.warn("device {} not found", name);
|
||||||
updateStatus(ThingStatus.OFFLINE);
|
deviceUpdate(null);
|
||||||
bridgeHandler = null;
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
State updateState = UnDefType.UNDEF;
|
State updateState = UnDefType.UNDEF;
|
||||||
|
@ -384,19 +353,20 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public int getLastVolume() {
|
public Integer getLastVolume() {
|
||||||
if (savedVolume == null) {
|
Integer savedVolumeFinal = savedVolume;
|
||||||
|
if (savedVolumeFinal == null) {
|
||||||
PulseaudioBridgeHandler briHandler = getPulseaudioBridgeHandler();
|
PulseaudioBridgeHandler briHandler = getPulseaudioBridgeHandler();
|
||||||
if (briHandler != null) {
|
if (briHandler != null) {
|
||||||
// refresh to get the current volume level
|
// refresh to get the current volume level
|
||||||
briHandler.getClient().update();
|
briHandler.getClient().update();
|
||||||
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
|
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
|
||||||
if (device != null) {
|
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) {
|
public void setVolume(int volume) {
|
||||||
|
@ -415,25 +385,38 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
savedVolume = volume;
|
savedVolume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void deviceUpdate(@Nullable AbstractAudioDeviceConfig device) {
|
||||||
public void onDeviceStateChanged(ThingUID bridge, AbstractAudioDeviceConfig device) {
|
if (device != null && device.getPaName().equals(name)) {
|
||||||
if (device.getPaName().equals(name)) {
|
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
logger.debug("Updating states of {} id: {}", device, VOLUME_CHANNEL);
|
logger.debug("Updating states of {} id: {}", device, VOLUME_CHANNEL);
|
||||||
savedVolume = device.getVolume();
|
int actualVolume = device.getVolume();
|
||||||
updateState(VOLUME_CHANNEL, new PercentType(savedVolume));
|
savedVolume = actualVolume;
|
||||||
updateState(MUTE_CHANNEL, device.isMuted() ? OnOffType.ON : OnOffType.OFF);
|
updateState(VOLUME_CHANNEL, new PercentType(actualVolume));
|
||||||
updateState(STATE_CHANNEL,
|
updateState(MUTE_CHANNEL, OnOffType.from(device.isMuted()));
|
||||||
device.getState() != null ? new StringType(device.getState().toString()) : new StringType("-"));
|
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) {
|
if (device instanceof SinkInput) {
|
||||||
updateState(ROUTE_TO_SINK_CHANNEL,
|
updateState(ROUTE_TO_SINK_CHANNEL, new StringType(
|
||||||
((SinkInput) device).getSink() != null
|
Optional.ofNullable(((SinkInput) device).getSink()).map(Sink::getPaName).orElse("-")));
|
||||||
? new StringType(((SinkInput) device).getSink().getPaName())
|
|
||||||
: new StringType("-"));
|
|
||||||
}
|
}
|
||||||
if (device instanceof Sink && ((Sink) device).isCombinedSink()) {
|
if (device instanceof Sink && ((Sink) device).isCombinedSink()) {
|
||||||
updateState(SLAVES_CHANNEL, new StringType(String.join(",", ((Sink) device).getCombinedSinkNames())));
|
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,
|
* 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 IOException when device info is not available
|
||||||
* @throws InterruptedException when interrupted during the loading module wait
|
* @throws InterruptedException when interrupted during the loading module wait
|
||||||
*/
|
*/
|
||||||
public int getSimpleTcpPort() throws IOException, InterruptedException {
|
public int getSimpleTcpPortAndLoadModuleIfNecessary() throws IOException, InterruptedException {
|
||||||
var briHandler = getPulseaudioBridgeHandler();
|
var briHandler = getPulseaudioBridgeHandler();
|
||||||
if (briHandler == null) {
|
if (briHandler == null) {
|
||||||
throw new IOException("bridge is not ready");
|
throw new IOException("bridge is not ready");
|
||||||
|
@ -515,43 +498,22 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIdleTimeout() {
|
public int getIdleTimeout() {
|
||||||
|
var idleTimeout = 3000;
|
||||||
var handler = getPulseaudioBridgeHandler();
|
var handler = getPulseaudioBridgeHandler();
|
||||||
if (handler == null) {
|
if (handler != null) {
|
||||||
return 30000;
|
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);
|
return idleTimeout;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBasicProtocolSOTimeout() {
|
public int getBasicProtocolSOTimeout() {
|
||||||
var soTimeout = (BigDecimal) getThing().getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOCKET_SO_TIMEOUT);
|
var soTimeout = (BigDecimal) getThing().getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOCKET_SO_TIMEOUT);
|
||||||
return soTimeout != null ? soTimeout.intValue() : 500;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,16 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal.items;
|
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
|
* GenericAudioItems are any kind of items that deal with audio data and can be
|
||||||
* muted or their volume can be changed.
|
* muted or their volume can be changed.
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public abstract class AbstractAudioDeviceConfig extends AbstractDeviceConfig {
|
public abstract class AbstractAudioDeviceConfig extends AbstractDeviceConfig {
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
|
@ -28,25 +32,21 @@ public abstract class AbstractAudioDeviceConfig extends AbstractDeviceConfig {
|
||||||
DRAINED
|
DRAINED
|
||||||
}
|
}
|
||||||
|
|
||||||
protected State state;
|
protected @Nullable State state;
|
||||||
protected boolean muted;
|
protected boolean muted;
|
||||||
protected int volume;
|
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);
|
super(id, name);
|
||||||
this.module = module;
|
this.module = module;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Module getModule() {
|
public @Nullable Module getModule() {
|
||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModule(Module module) {
|
public @Nullable State getState() {
|
||||||
this.module = module;
|
|
||||||
}
|
|
||||||
|
|
||||||
public State getState() {
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal.items;
|
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
|
* 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
|
* 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
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public abstract class AbstractDeviceConfig {
|
public abstract class AbstractDeviceConfig {
|
||||||
|
|
||||||
protected int id;
|
protected int id;
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal.items;
|
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
|
* In order to add a {@link Sink} to the pulseaudio server you have to
|
||||||
* load a corresponding module. Current Module objects are needed 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
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class Module extends AbstractDeviceConfig {
|
public class Module extends AbstractDeviceConfig {
|
||||||
|
|
||||||
private String argument;
|
private @Nullable String argument;
|
||||||
|
|
||||||
public Module(int id, String name) {
|
public Module(int id, String name) {
|
||||||
super(id, name);
|
super(id, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getArgument() {
|
public @Nullable String getArgument() {
|
||||||
return argument;
|
return argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@ package org.openhab.binding.pulseaudio.internal.items;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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
|
* 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
|
* (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
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class Sink extends AbstractAudioDeviceConfig {
|
public class Sink extends AbstractAudioDeviceConfig {
|
||||||
|
|
||||||
protected List<String> combinedSinkNames;
|
protected List<String> combinedSinkNames;
|
||||||
protected List<Sink> combinedSinks;
|
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);
|
super(id, name, module);
|
||||||
combinedSinkNames = new ArrayList<>();
|
combinedSinkNames = new ArrayList<>();
|
||||||
combinedSinks = new ArrayList<>();
|
combinedSinks = new ArrayList<>();
|
||||||
|
@ -53,7 +57,7 @@ public class Sink extends AbstractAudioDeviceConfig {
|
||||||
this.combinedSinks = combinedSinks;
|
this.combinedSinks = combinedSinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCombinedSink(Sink sink) {
|
public void addCombinedSink(@Nullable Sink sink) {
|
||||||
if (sink != null) {
|
if (sink != null) {
|
||||||
this.combinedSinks.add(sink);
|
this.combinedSinks.add(sink);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,24 +12,29 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal.items;
|
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}
|
* A SinkInput is an audio stream which can be routed to a {@link Sink}
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class SinkInput extends AbstractAudioDeviceConfig {
|
public class SinkInput extends AbstractAudioDeviceConfig {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private Sink sink;
|
private Sink sink;
|
||||||
|
|
||||||
public SinkInput(int id, String name, Module module) {
|
public SinkInput(int id, String name, @Nullable Module module) {
|
||||||
super(id, name, module);
|
super(id, name, module);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sink getSink() {
|
public @Nullable Sink getSink() {
|
||||||
return sink;
|
return sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSink(Sink sink) {
|
public void setSink(@Nullable Sink sink) {
|
||||||
this.sink = sink;
|
this.sink = sink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,25 +12,30 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal.items;
|
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
|
* A Source is a device which is the source of an audio stream (recording
|
||||||
* device) For example microphones or line-in jacks.
|
* device) For example microphones or line-in jacks.
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class Source extends AbstractAudioDeviceConfig {
|
public class Source extends AbstractAudioDeviceConfig {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
protected Sink monitorOf;
|
protected Sink monitorOf;
|
||||||
|
|
||||||
public Source(int id, String name, Module module) {
|
public Source(int id, String name, @Nullable Module module) {
|
||||||
super(id, name, module);
|
super(id, name, module);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sink getMonitorOf() {
|
public @Nullable Sink getMonitorOf() {
|
||||||
return monitorOf;
|
return monitorOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMonitorOf(Sink sink) {
|
public void setMonitorOf(@Nullable Sink sink) {
|
||||||
this.monitorOf = sink;
|
this.monitorOf = sink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,24 +12,29 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.pulseaudio.internal.items;
|
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}
|
* A SourceOutput is the audio stream which is produced by a (@link Source}
|
||||||
*
|
*
|
||||||
* @author Tobias Bräutigam - Initial contribution
|
* @author Tobias Bräutigam - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class SourceOutput extends AbstractAudioDeviceConfig {
|
public class SourceOutput extends AbstractAudioDeviceConfig {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private Source source;
|
private Source source;
|
||||||
|
|
||||||
public SourceOutput(int id, String name, Module module) {
|
public SourceOutput(int id, String name, @Nullable Module module) {
|
||||||
super(id, name, module);
|
super(id, name, module);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Source getSource() {
|
public @Nullable Source getSource() {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSource(Source source) {
|
public void setSource(@Nullable Source source) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue