2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.pulseaudio.internal;
15 import java.io.IOException;
16 import java.net.Socket;
17 import java.util.Locale;
18 import java.util.concurrent.ScheduledExecutorService;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.locks.ReentrantLock;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.pulseaudio.internal.handler.PulseaudioHandler;
26 import org.openhab.core.library.types.PercentType;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * A connection to a pulseaudio Simple TCP Protocol
33 * @author Gwendal Roulleau - Initial contribution
34 * @author Miguel Álvarez - Refactor some code from PulseAudioAudioSink here
38 public abstract class PulseaudioSimpleProtocolStream {
40 private final Logger logger = LoggerFactory.getLogger(PulseaudioSimpleProtocolStream.class);
42 protected PulseaudioHandler pulseaudioHandler;
43 protected ScheduledExecutorService scheduler;
45 protected @Nullable Socket clientSocket;
47 private ReentrantLock countClientLock = new ReentrantLock();
48 private Integer countClient = 0;
50 private @Nullable ScheduledFuture<?> scheduledDisconnection;
52 public PulseaudioSimpleProtocolStream(PulseaudioHandler pulseaudioHandler, ScheduledExecutorService scheduler) {
53 this.pulseaudioHandler = pulseaudioHandler;
54 this.scheduler = scheduler;
58 * Connect to pulseaudio with the simple protocol
59 * Will schedule an attempt for disconnection after timeout
62 * @throws InterruptedException when interrupted during the loading module wait
64 public void connectIfNeeded() throws IOException, InterruptedException {
65 Socket clientSocketLocal = clientSocket;
66 if (clientSocketLocal == null || !clientSocketLocal.isConnected() || clientSocketLocal.isClosed()) {
67 logger.debug("Simple TCP Stream connecting for {}", getLabel(null));
68 String host = pulseaudioHandler.getHost();
69 int port = pulseaudioHandler.getSimpleTcpPortAndLoadModuleIfNecessary();
70 var clientSocketFinal = new Socket(host, port);
71 clientSocketFinal.setSoTimeout(pulseaudioHandler.getBasicProtocolSOTimeout());
72 clientSocket = clientSocketFinal;
73 scheduleDisconnectIfNoClient();
78 * Disconnect the socket to pulseaudio simple protocol
80 public void disconnect() {
81 final Socket clientSocketLocal = clientSocket;
82 if (clientSocketLocal != null) {
83 logger.debug("Simple TCP Stream disconnecting for {}", getLabel(null));
85 clientSocketLocal.close();
86 } catch (IOException ignored) {
89 logger.debug("Stream still running or socket not open");
93 private void scheduleDisconnectIfNoClient() {
94 countClientLock.lock();
96 if (countClient <= 0) {
97 var scheduledDisconnectionFinal = scheduledDisconnection;
98 if (scheduledDisconnectionFinal != null) {
99 logger.debug("Aborting next disconnect");
100 scheduledDisconnectionFinal.cancel(true);
102 int idleTimeout = pulseaudioHandler.getIdleTimeout();
103 if (idleTimeout > -1) {
104 if (idleTimeout == 0) {
107 logger.debug("Scheduling next disconnect");
108 scheduledDisconnection = scheduler.schedule(this::disconnect, idleTimeout,
109 TimeUnit.MILLISECONDS);
114 countClientLock.unlock();
118 public PercentType getVolume() {
119 return new PercentType(pulseaudioHandler.getLastVolume());
122 public void setVolume(PercentType volume) {
123 pulseaudioHandler.setVolume(volume.intValue());
126 public String getId() {
127 return pulseaudioHandler.getThing().getUID().toString();
130 public String getLabel(@Nullable Locale locale) {
131 var label = pulseaudioHandler.getThing().getLabel();
132 return label != null ? label : pulseaudioHandler.getThing().getUID().getId();
135 protected void addClientCount() {
136 countClientLock.lock();
139 logger.debug("Adding new client for pulseaudio sink/source {}. Current count: {}", getLabel(null),
141 if (countClient <= 0) { // safe against misuse
144 var scheduledDisconnectionFinal = scheduledDisconnection;
145 if (scheduledDisconnectionFinal != null) {
146 logger.debug("Aborting next disconnect");
147 scheduledDisconnectionFinal.cancel(true);
150 countClientLock.unlock();
154 protected void minusClientCount() {
155 countClientLock.lock();
157 logger.debug("Removing client for pulseaudio sink/source {}. Current count: {}", getLabel(null), countClient);
158 if (countClient < 0) { // safe against misuse
161 countClientLock.unlock();
162 if (countClient <= 0) {
163 scheduleDisconnectIfNoClient();