]> git.basschouten.com Git - openhab-addons.git/blob
077bb5881a4642530ab9463bd39ffacad296e249
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.pulseaudio.internal;
14
15 import java.io.IOException;
16 import java.net.Socket;
17 import java.time.Duration;
18 import java.time.Instant;
19 import java.util.HashSet;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledExecutorService;
22
23 import javax.sound.sampled.UnsupportedAudioFileException;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.pulseaudio.internal.handler.PulseaudioHandler;
28 import org.openhab.core.audio.AudioFormat;
29 import org.openhab.core.audio.AudioSink;
30 import org.openhab.core.audio.AudioStream;
31 import org.openhab.core.audio.FixedLengthAudioStream;
32 import org.openhab.core.audio.UnsupportedAudioFormatException;
33 import org.openhab.core.audio.UnsupportedAudioStreamException;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * The audio sink for openhab, implemented by a connection to a pulseaudio sink
39  *
40  * @author Gwendal Roulleau - Initial contribution
41  * @author Miguel Álvarez - move some code to the PulseaudioSimpleProtocolStream class so sink and source can extend
42  *         from it.
43  *
44  */
45 @NonNullByDefault
46 public class PulseAudioAudioSink extends PulseaudioSimpleProtocolStream implements AudioSink {
47
48     private final Logger logger = LoggerFactory.getLogger(PulseAudioAudioSink.class);
49
50     private static final HashSet<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
51     private static final HashSet<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>();
52
53     static {
54         SUPPORTED_FORMATS.add(AudioFormat.WAV);
55         SUPPORTED_FORMATS.add(AudioFormat.MP3);
56         SUPPORTED_STREAMS.add(FixedLengthAudioStream.class);
57     }
58
59     public PulseAudioAudioSink(PulseaudioHandler pulseaudioHandler, ScheduledExecutorService scheduler) {
60         super(pulseaudioHandler, scheduler);
61     }
62
63     @Override
64     public void process(@Nullable AudioStream audioStream)
65             throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
66         if (audioStream == null) {
67             return;
68         }
69         addClientCount();
70         try (ConvertedInputStream normalizedPCMStream = new ConvertedInputStream(audioStream)) {
71             for (int countAttempt = 1; countAttempt <= 2; countAttempt++) { // two attempts allowed
72                 try {
73                     connectIfNeeded();
74                     final Socket clientSocketLocal = clientSocket;
75                     if (clientSocketLocal != null) {
76                         // send raw audio to the socket and to pulse audio
77                         Instant start = Instant.now();
78                         normalizedPCMStream.transferTo(clientSocketLocal.getOutputStream());
79                         if (normalizedPCMStream.getDuration() != -1) { // ensure, if the sound has a duration
80                             // that we let at least this time for the system to play
81                             Instant end = Instant.now();
82                             long millisSecondTimedToSendAudioData = Duration.between(start, end).toMillis();
83                             if (millisSecondTimedToSendAudioData < normalizedPCMStream.getDuration()) {
84                                 long timeToSleep = normalizedPCMStream.getDuration() - millisSecondTimedToSendAudioData;
85                                 logger.debug("Sleep time to let the system play sound : {}", timeToSleep);
86                                 Thread.sleep(timeToSleep);
87                             }
88                         }
89                         break;
90                     }
91                 } catch (IOException e) {
92                     disconnect(); // disconnect force to clear connection in case of socket not cleanly shutdown
93                     if (countAttempt == 2) { // we won't retry : log and quit
94                         final Socket clientSocketLocal = clientSocket;
95                         String port = clientSocketLocal != null ? Integer.toString(clientSocketLocal.getPort())
96                                 : "unknown";
97                         logger.warn(
98                                 "Error while trying to send audio to pulseaudio audio sink. Cannot connect to {}:{}, error: {}",
99                                 pulseaudioHandler.getHost(), port, e.getMessage());
100                         break;
101                     }
102                 } catch (InterruptedException ie) {
103                     logger.info("Interrupted during sink audio connection: {}", ie.getMessage());
104                     break;
105                 }
106             }
107         } catch (UnsupportedAudioFileException | IOException e) {
108             throw new UnsupportedAudioFormatException("Cannot send sound to the pulseaudio sink",
109                     audioStream.getFormat(), e);
110         } finally {
111             minusClientCount();
112         }
113     }
114
115     @Override
116     public Set<AudioFormat> getSupportedFormats() {
117         return SUPPORTED_FORMATS;
118     }
119
120     @Override
121     public Set<Class<? extends AudioStream>> getSupportedStreams() {
122         return SUPPORTED_STREAMS;
123     }
124 }