[freebox] Support for more audio streams through the HTTP audio servlet (#15121)

Related to #15113

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2023-07-12 21:50:20 +02:00 committed by GitHub
parent 25314d408f
commit af89237d6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 46 additions and 29 deletions

View File

@ -15,6 +15,7 @@ package org.openhab.binding.freebox.internal;
import static org.openhab.core.audio.AudioFormat.*; import static org.openhab.core.audio.AudioFormat.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
@ -27,8 +28,9 @@ import org.openhab.binding.freebox.internal.handler.FreeboxThingHandler;
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.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.UnsupportedAudioStreamException;
@ -43,9 +45,10 @@ import org.slf4j.LoggerFactory;
* This makes an AirPlay device to serve as an {@link AudioSink}- * This makes an AirPlay device to serve as an {@link AudioSink}-
* *
* @author Laurent Garnier - Initial contribution for AudioSink and notifications * @author Laurent Garnier - Initial contribution for AudioSink and notifications
* @author Laurent Garnier - Support for more audio streams through the HTTP audio servlet
*/ */
@NonNullByDefault @NonNullByDefault
public class FreeboxAirPlayAudioSink implements AudioSink { public class FreeboxAirPlayAudioSink extends AudioSinkAsync {
private final Logger logger = LoggerFactory.getLogger(FreeboxAirPlayAudioSink.class); 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 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 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 AudioHTTPServer audioHTTPServer;
private FreeboxThingHandler handler; private FreeboxThingHandler handler;
private @Nullable String callbackUrl; private @Nullable String callbackUrl;
static {
SUPPORTED_STREAMS.add(AudioStream.class);
}
public FreeboxAirPlayAudioSink(FreeboxThingHandler handler, AudioHTTPServer audioHTTPServer, public FreeboxAirPlayAudioSink(FreeboxThingHandler handler, AudioHTTPServer audioHTTPServer,
@Nullable String callbackUrl) { @Nullable String callbackUrl) {
this.handler = handler; this.handler = handler;
@ -89,7 +88,8 @@ public class FreeboxAirPlayAudioSink implements AudioSink {
SUPPORTED_FORMATS.add(MP3_256); SUPPORTED_FORMATS.add(MP3_256);
SUPPORTED_FORMATS.add(MP3_320); 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 @Override
@ -103,13 +103,14 @@ public class FreeboxAirPlayAudioSink implements AudioSink {
} }
@Override @Override
public void process(@Nullable AudioStream audioStream) protected void processAsynchronously(@Nullable AudioStream audioStream)
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
if (!ThingHandlerHelper.isHandlerInitialized(handler) if (!ThingHandlerHelper.isHandlerInitialized(handler)
|| ((handler.getThing().getStatus() == ThingStatus.OFFLINE) || ((handler.getThing().getStatus() == ThingStatus.OFFLINE)
&& ((handler.getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) && ((handler.getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE)
|| (handler.getThing().getStatusInfo() || (handler.getThing().getStatusInfo()
.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR)))) { .getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR)))) {
tryClose(audioStream);
return; return;
} }
@ -122,29 +123,36 @@ public class FreeboxAirPlayAudioSink implements AudioSink {
return; return;
} }
String url = null; String url;
if (audioStream instanceof URLAudioStream) { if (audioStream instanceof URLAudioStream urlAudioStream) {
// it is an external URL, we can access it directly // it is an external URL, we can access it directly
URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
url = urlAudioStream.getURL(); url = urlAudioStream.getURL();
} else { tryClose(audioStream);
if (callbackUrl != null) { } else if (callbackUrl != null) {
// we serve it on our own HTTP server // we serve it on our own HTTP server
String relativeUrl; logger.debug("audioStream {} {}", audioStream.getClass().getSimpleName(), audioStream.getFormat());
if (audioStream instanceof FixedLengthAudioStream) { StreamServed streamServed;
relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 20); try {
} else { streamServed = audioHTTPServer.serve(audioStream, 5, true);
relativeUrl = audioHTTPServer.serve(audioStream); } catch (IOException e) {
} tryClose(audioStream);
url = callbackUrl + relativeUrl; throw new UnsupportedAudioStreamException(
} else { "AirPlay device was not able to handle the audio stream (cache on disk failed).",
logger.warn("We do not have any callback url, so AirPlay device cannot play the audio stream!"); audioStream.getClass(), e);
} }
} url = callbackUrl + streamServed.url();
try { streamServed.playEnd().thenRun(() -> {
audioStream.close(); try {
} catch (IOException e) { handler.stopMedia();
logger.debug("Exception while closing audioStream", e); } 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 { try {
logger.debug("AirPlay audio sink: process url {}", url); logger.debug("AirPlay audio sink: process url {}", url);
@ -154,6 +162,15 @@ public class FreeboxAirPlayAudioSink implements AudioSink {
} }
} }
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_FORMATS; return SUPPORTED_FORMATS;