Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Jul 12, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;
Expand Down