]> git.basschouten.com Git - openhab-addons.git/blob
d8aa303d76cfad90c2b0ba69f19935933f199ec8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.amplipi.internal.audio;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.Locale;
18 import java.util.Set;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.amplipi.internal.AmpliPiHandler;
23 import org.openhab.core.audio.AudioFormat;
24 import org.openhab.core.audio.AudioSinkSync;
25 import org.openhab.core.audio.AudioStream;
26 import org.openhab.core.audio.StreamServed;
27 import org.openhab.core.audio.URLAudioStream;
28 import org.openhab.core.audio.UnsupportedAudioFormatException;
29 import org.openhab.core.audio.UnsupportedAudioStreamException;
30 import org.openhab.core.library.types.PercentType;
31 import org.openhab.core.thing.binding.ThingHandler;
32 import org.openhab.core.thing.binding.ThingHandlerService;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * This is an audio sink that allows to do public announcements on the AmpliPi.
38  *
39  * @author Kai Kreuzer - Initial contribution
40  *
41  */
42 @NonNullByDefault
43 public class PAAudioSink extends AudioSinkSync implements ThingHandlerService {
44
45     private final Logger logger = LoggerFactory.getLogger(PAAudioSink.class);
46
47     private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV);
48     private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Set.of(AudioStream.class);
49
50     private @Nullable AmpliPiHandler handler;
51
52     private @Nullable PercentType volume;
53
54     @Override
55     protected void processSynchronously(@Nullable AudioStream audioStream)
56             throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
57         if (audioStream == null) {
58             // in case the audioStream is null, this should be interpreted as a request to end any currently playing
59             // stream.
60             logger.debug("AmpliPi sink does not support stopping the currently playing stream.");
61             return;
62         }
63         AmpliPiHandler localHandler = this.handler;
64         if (localHandler == null) {
65             tryClose(audioStream);
66             return;
67         }
68         logger.debug("Received audio stream of format {}", audioStream.getFormat());
69         String callbackUrl = localHandler.getCallbackUrl();
70         String audioUrl;
71         if (audioStream instanceof URLAudioStream urlAudioStream) {
72             // it is an external URL, so we can directly pass this on.
73             audioUrl = urlAudioStream.getURL();
74             tryClose(audioStream);
75         } else if (callbackUrl != null) {
76             // we need to serve it for a while
77             StreamServed streamServed;
78             try {
79                 streamServed = localHandler.getAudioHTTPServer().serve(audioStream, 10, true);
80             } catch (IOException e) {
81                 tryClose(audioStream);
82                 throw new UnsupportedAudioStreamException(
83                         "AmpliPi was not able to handle the audio stream (cache on disk failed).",
84                         audioStream.getClass(), e);
85             }
86             audioUrl = callbackUrl + streamServed.url();
87         } else {
88             logger.warn("We do not have any callback url, so AmpliPi cannot play the audio stream!");
89             tryClose(audioStream);
90             return;
91         }
92         localHandler.playPA(audioUrl, volume);
93         // we reset the volume value again, so that a next invocation without a volume will again use the zones
94         // defaults.
95         volume = null;
96     }
97
98     private void tryClose(@Nullable InputStream is) {
99         if (is != null) {
100             try {
101                 is.close();
102             } catch (IOException ignored) {
103             }
104         }
105     }
106
107     @Override
108     public Set<AudioFormat> getSupportedFormats() {
109         return SUPPORTED_AUDIO_FORMATS;
110     }
111
112     @Override
113     public Set<Class<? extends AudioStream>> getSupportedStreams() {
114         return SUPPORTED_AUDIO_STREAMS;
115     }
116
117     @Override
118     public String getId() {
119         if (handler != null) {
120             return handler.getThing().getUID().toString();
121         } else {
122             throw new IllegalStateException();
123         }
124     }
125
126     @Override
127     public @Nullable String getLabel(@Nullable Locale locale) {
128         if (handler != null) {
129             return handler.getThing().getLabel();
130         } else {
131             return null;
132         }
133     }
134
135     @Override
136     public PercentType getVolume() throws IOException {
137         PercentType vol = volume;
138         if (vol != null) {
139             return vol;
140         } else {
141             throw new IOException("Audio sink does not support reporting the volume.");
142         }
143     }
144
145     @Override
146     public void setVolume(final PercentType volume) throws IOException {
147         this.volume = volume;
148     }
149
150     @Override
151     public void setThingHandler(ThingHandler handler) {
152         this.handler = (AmpliPiHandler) handler;
153     }
154
155     @Override
156     public @Nullable ThingHandler getThingHandler() {
157         return handler;
158     }
159 }