[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:
parent
25314d408f
commit
af89237d6b
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue