From 5254dac2441d0523383e2f2d1fa12815c11f824f Mon Sep 17 00:00:00 2001 From: lolodomo <lg.hc@free.fr> Date: Wed, 12 Jul 2023 21:50:20 +0200 Subject: [PATCH] [freebox] Support for more audio streams through the HTTP audio servlet (#15121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Related to #15113 Signed-off-by: Laurent Garnier <lg.hc@free.fr> Signed-off-by: Jørgen Austvik <jaustvik@acm.org> --- .../internal/FreeboxAirPlayAudioSink.java | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/bundles/org.openhab.binding.freebox/src/main/java/org/openhab/binding/freebox/internal/FreeboxAirPlayAudioSink.java b/bundles/org.openhab.binding.freebox/src/main/java/org/openhab/binding/freebox/internal/FreeboxAirPlayAudioSink.java index aff314d448684..7050291643834 100644 --- a/bundles/org.openhab.binding.freebox/src/main/java/org/openhab/binding/freebox/internal/FreeboxAirPlayAudioSink.java +++ b/bundles/org.openhab.binding.freebox/src/main/java/org/openhab/binding/freebox/internal/FreeboxAirPlayAudioSink.java @@ -15,6 +15,7 @@ import static org.openhab.core.audio.AudioFormat.*; import java.io.IOException; +import java.io.InputStream; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -27,8 +28,9 @@ import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioHTTPServer; import org.openhab.core.audio.AudioSink; +import org.openhab.core.audio.AudioSinkAsync; import org.openhab.core.audio.AudioStream; -import org.openhab.core.audio.FixedLengthAudioStream; +import org.openhab.core.audio.StreamServed; import org.openhab.core.audio.URLAudioStream; import org.openhab.core.audio.UnsupportedAudioFormatException; import org.openhab.core.audio.UnsupportedAudioStreamException; @@ -43,9 +45,10 @@ * This makes an AirPlay device to serve as an {@link AudioSink}- * * @author Laurent Garnier - Initial contribution for AudioSink and notifications + * @author Laurent Garnier - Support for more audio streams through the HTTP audio servlet */ @NonNullByDefault -public class FreeboxAirPlayAudioSink implements AudioSink { +public class FreeboxAirPlayAudioSink extends AudioSinkAsync { private final Logger logger = LoggerFactory.getLogger(FreeboxAirPlayAudioSink.class); @@ -59,15 +62,11 @@ public class FreeboxAirPlayAudioSink implements AudioSink { private static final AudioFormat MP3_320 = new AudioFormat(CONTAINER_NONE, CODEC_MP3, null, null, 320000, null); private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>(); - private static final HashSet<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>(); + private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Set.of(AudioStream.class); private AudioHTTPServer audioHTTPServer; private FreeboxThingHandler handler; private @Nullable String callbackUrl; - static { - SUPPORTED_STREAMS.add(AudioStream.class); - } - public FreeboxAirPlayAudioSink(FreeboxThingHandler handler, AudioHTTPServer audioHTTPServer, @Nullable String callbackUrl) { this.handler = handler; @@ -89,7 +88,8 @@ public FreeboxAirPlayAudioSink(FreeboxThingHandler handler, AudioHTTPServer audi SUPPORTED_FORMATS.add(MP3_256); SUPPORTED_FORMATS.add(MP3_320); } - SUPPORTED_FORMATS.add(OGG); + // OGG seems to not be properly supported (tested with a file produced by VoiceRSS) + // SUPPORTED_FORMATS.add(OGG); } @Override @@ -103,13 +103,14 @@ public String getId() { } @Override - public void process(@Nullable AudioStream audioStream) + protected void processAsynchronously(@Nullable AudioStream audioStream) throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { if (!ThingHandlerHelper.isHandlerInitialized(handler) || ((handler.getThing().getStatus() == ThingStatus.OFFLINE) && ((handler.getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) || (handler.getThing().getStatusInfo() .getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR)))) { + tryClose(audioStream); return; } @@ -122,29 +123,36 @@ public void process(@Nullable AudioStream audioStream) return; } - String url = null; - if (audioStream instanceof URLAudioStream) { + String url; + if (audioStream instanceof URLAudioStream urlAudioStream) { // it is an external URL, we can access it directly - URLAudioStream urlAudioStream = (URLAudioStream) audioStream; url = urlAudioStream.getURL(); - } else { - if (callbackUrl != null) { - // we serve it on our own HTTP server - String relativeUrl; - if (audioStream instanceof FixedLengthAudioStream) { - relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 20); - } else { - relativeUrl = audioHTTPServer.serve(audioStream); - } - url = callbackUrl + relativeUrl; - } else { - logger.warn("We do not have any callback url, so AirPlay device cannot play the audio stream!"); + tryClose(audioStream); + } else if (callbackUrl != null) { + // we serve it on our own HTTP server + logger.debug("audioStream {} {}", audioStream.getClass().getSimpleName(), audioStream.getFormat()); + StreamServed streamServed; + try { + streamServed = audioHTTPServer.serve(audioStream, 5, true); + } catch (IOException e) { + tryClose(audioStream); + throw new UnsupportedAudioStreamException( + "AirPlay device was not able to handle the audio stream (cache on disk failed).", + audioStream.getClass(), e); } - } - try { - audioStream.close(); - } catch (IOException e) { - logger.debug("Exception while closing audioStream", e); + url = callbackUrl + streamServed.url(); + streamServed.playEnd().thenRun(() -> { + try { + handler.stopMedia(); + } catch (FreeboxException e) { + logger.warn("Exception while stopping audio stream playback: {}", e.getMessage()); + } + this.playbackFinished(audioStream); + }); + } else { + logger.warn("We do not have any callback url, so AirPlay device cannot play the audio stream!"); + tryClose(audioStream); + return; } try { logger.debug("AirPlay audio sink: process url {}", url); @@ -154,6 +162,15 @@ public void process(@Nullable AudioStream audioStream) } } + private void tryClose(@Nullable InputStream is) { + if (is != null) { + try { + is.close(); + } catch (IOException ignored) { + } + } + } + @Override public Set<AudioFormat> getSupportedFormats() { return SUPPORTED_FORMATS;