[heos] Support for more audio streams through the HTTP audio servlet (#15196)

Related to #15113

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2023-07-12 14:30:00 +02:00 committed by GitHub
parent 5c32f80c3b
commit 7c9f66ffb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 69 additions and 44 deletions

View File

@ -13,7 +13,7 @@
package org.openhab.binding.heos.internal.api; package org.openhab.binding.heos.internal.api;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.io.InputStream;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
@ -24,11 +24,13 @@ import org.openhab.binding.heos.internal.resources.Telnet.ReadException;
import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioHTTPServer; import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink; import org.openhab.core.audio.AudioSink;
import org.openhab.core.audio.AudioSinkAsync;
import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.FileAudioStream; import org.openhab.core.audio.FileAudioStream;
import org.openhab.core.audio.FixedLengthAudioStream; import org.openhab.core.audio.StreamServed;
import org.openhab.core.audio.URLAudioStream; import org.openhab.core.audio.URLAudioStream;
import org.openhab.core.audio.UnsupportedAudioFormatException; import org.openhab.core.audio.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
import org.openhab.core.audio.utils.AudioStreamUtils; import org.openhab.core.audio.utils.AudioStreamUtils;
import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.util.ThingHandlerHelper; import org.openhab.core.thing.util.ThingHandlerHelper;
@ -39,22 +41,15 @@ import org.slf4j.LoggerFactory;
* This makes HEOS to serve as an {@link AudioSink}. * This makes HEOS to serve as an {@link AudioSink}.
* *
* @author Johannes Einig - Initial contribution * @author Johannes Einig - Initial contribution
* @author Laurent Garnier - Extend AudioSinkAsync
*/ */
@NonNullByDefault @NonNullByDefault
public class HeosAudioSink implements AudioSink { public class HeosAudioSink extends AudioSinkAsync {
private final Logger logger = LoggerFactory.getLogger(HeosAudioSink.class); private final Logger logger = LoggerFactory.getLogger(HeosAudioSink.class);
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = new HashSet<>(); private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.WAV, AudioFormat.MP3,
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = new HashSet<>(); AudioFormat.AAC);
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Set.of(AudioStream.class);
static {
SUPPORTED_AUDIO_FORMATS.add(AudioFormat.WAV);
SUPPORTED_AUDIO_FORMATS.add(AudioFormat.MP3);
SUPPORTED_AUDIO_FORMATS.add(AudioFormat.AAC);
SUPPORTED_AUDIO_STREAMS.add(URLAudioStream.class);
SUPPORTED_AUDIO_STREAMS.add(FixedLengthAudioStream.class);
}
private final HeosThingBaseHandler handler; private final HeosThingBaseHandler handler;
private final AudioHTTPServer audioHTTPServer; private final AudioHTTPServer audioHTTPServer;
@ -77,42 +72,72 @@ public class HeosAudioSink implements AudioSink {
} }
@Override @Override
public void process(@Nullable AudioStream audioStream) throws UnsupportedAudioFormatException { protected void processAsynchronously(@Nullable AudioStream audioStream)
try { throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
if (audioStream instanceof URLAudioStream) { if (!ThingHandlerHelper.isHandlerInitialized(handler)) {
// it is an external URL, the speaker can access it itself and play it. logger.debug("HEOS speaker '{}' is not initialized - status is {}", handler.getThing().getUID(),
URLAudioStream urlAudioStream = (URLAudioStream) audioStream; handler.getThing().getStatus());
handler.playURL(urlAudioStream.getURL()); tryClose(audioStream);
} else if (audioStream instanceof FixedLengthAudioStream) { return;
if (callbackUrl != null) { }
// we serve it on our own HTTP server for 30 seconds as HEOS requests the stream several times
String relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 30); if (audioStream == null) {
String url = callbackUrl + relativeUrl + AudioStreamUtils.EXTENSION_SEPARATOR; return;
AudioFormat audioFormat = audioStream.getFormat(); }
if (!ThingHandlerHelper.isHandlerInitialized(handler)) {
logger.debug("HEOS speaker '{}' is not initialized - status is {}", handler.getThing().getUID(), AudioFormat audioFormat = audioStream.getFormat();
handler.getThing().getStatus()); if (!AudioFormat.MP3.isCompatible(audioFormat) && !AudioFormat.WAV.isCompatible(audioFormat)
} else if (AudioFormat.MP3.isCompatible(audioFormat)) { && !AudioFormat.AAC.isCompatible(audioFormat)) {
handler.playURL(url + FileAudioStream.MP3_EXTENSION); tryClose(audioStream);
} else if (AudioFormat.WAV.isCompatible(audioFormat)) { throw new UnsupportedAudioFormatException("HEOS speaker only supports MP3, WAV and AAC formats.",
handler.playURL(url + FileAudioStream.WAV_EXTENSION); audioFormat);
} else if (AudioFormat.AAC.isCompatible(audioFormat)) { }
handler.playURL(url + FileAudioStream.AAC_EXTENSION);
} else { String url;
throw new UnsupportedAudioFormatException("HEOS only supports MP3, WAV and AAC.", audioFormat); if (audioStream instanceof URLAudioStream urlAudioStream) {
} // it is an external URL, the speaker can access it itself and play it.
} else { url = urlAudioStream.getURL();
logger.warn("We do not have any callback url, so HEOS cannot play the audio stream!"); tryClose(audioStream);
} } else if (callbackUrl != null) {
} else { StreamServed streamServed;
throw new UnsupportedAudioFormatException( try {
"HEOS can only handle FixedLengthAudioStreams & URLAudioStream.", null); streamServed = audioHTTPServer.serve(audioStream, 10, true);
} catch (IOException e) {
tryClose(audioStream);
throw new UnsupportedAudioStreamException(
"HEOS was not able to handle the audio stream (cache on disk failed).", audioStream.getClass(),
e);
} }
url = callbackUrl + streamServed.url() + AudioStreamUtils.EXTENSION_SEPARATOR;
if (AudioFormat.MP3.isCompatible(audioFormat)) {
url += FileAudioStream.MP3_EXTENSION;
} else if (AudioFormat.WAV.isCompatible(audioFormat)) {
url += FileAudioStream.WAV_EXTENSION;
} else if (AudioFormat.AAC.isCompatible(audioFormat)) {
url += FileAudioStream.AAC_EXTENSION;
}
streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream));
} else {
logger.warn("We do not have any callback url, so HEOS cannot play the audio stream!");
tryClose(audioStream);
return;
}
try {
handler.playURL(url);
} catch (IOException | ReadException e) { } catch (IOException | ReadException e) {
logger.warn("Failed to play audio stream: {}", e.getMessage()); logger.warn("Failed to play audio stream: {}", e.getMessage());
} }
} }
private void tryClose(@Nullable InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException ignored) {
}
}
}
@Override @Override
public Set<AudioFormat> getSupportedFormats() { public Set<AudioFormat> getSupportedFormats() {
return SUPPORTED_AUDIO_FORMATS; return SUPPORTED_AUDIO_FORMATS;