[allplay] Support for more audio streams through the HTTP audio servlet (#15201)
* [allplay] Support for more audio streams through the HTTP audio servlet Related to #15113 Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
@@ -13,15 +13,18 @@
|
|||||||
package org.openhab.binding.allplay.internal;
|
package org.openhab.binding.allplay.internal;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.allplay.internal.handler.AllPlayHandler;
|
import org.openhab.binding.allplay.internal.handler.AllPlayHandler;
|
||||||
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.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;
|
||||||
@@ -36,23 +39,16 @@ import de.kaizencode.tchaikovsky.exception.SpeakerException;
|
|||||||
*
|
*
|
||||||
* @author Dominic Lerbs - Initial contribution
|
* @author Dominic Lerbs - Initial contribution
|
||||||
*/
|
*/
|
||||||
public class AllPlayAudioSink implements AudioSink {
|
public class AllPlayAudioSink extends AudioSinkAsync {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(AllPlayAudioSink.class);
|
private final Logger logger = LoggerFactory.getLogger(AllPlayAudioSink.class);
|
||||||
|
|
||||||
private static final HashSet<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
|
private static final Set<AudioFormat> SUPPORTED_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV);
|
||||||
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 final AllPlayHandler handler;
|
private final AllPlayHandler handler;
|
||||||
private final AudioHTTPServer audioHTTPServer;
|
private final AudioHTTPServer audioHTTPServer;
|
||||||
private final String callbackUrl;
|
private final String callbackUrl;
|
||||||
|
|
||||||
static {
|
|
||||||
SUPPORTED_FORMATS.add(AudioFormat.MP3);
|
|
||||||
SUPPORTED_FORMATS.add(AudioFormat.WAV);
|
|
||||||
|
|
||||||
SUPPORTED_STREAMS.add(AudioStream.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param handler The related {@link AllPlayHandler}
|
* @param handler The related {@link AllPlayHandler}
|
||||||
* @param audioHTTPServer The {@link AudioHTTPServer} for serving the stream
|
* @param audioHTTPServer The {@link AudioHTTPServer} for serving the stream
|
||||||
@@ -75,13 +71,41 @@ public class AllPlayAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(AudioStream audioStream)
|
protected void processAsynchronously(@Nullable AudioStream audioStream)
|
||||||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
||||||
|
if (audioStream == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String url;
|
||||||
|
if (audioStream instanceof URLAudioStream urlAudioStream) {
|
||||||
|
// it is an external URL, the speaker can access it itself and play it
|
||||||
|
url = urlAudioStream.getURL();
|
||||||
|
tryClose(audioStream);
|
||||||
|
} else if (callbackUrl != null) {
|
||||||
|
StreamServed streamServed;
|
||||||
|
try {
|
||||||
|
streamServed = audioHTTPServer.serve(audioStream, 10, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
tryClose(audioStream);
|
||||||
|
throw new UnsupportedAudioStreamException(
|
||||||
|
"AllPlay was not able to handle the audio stream (cache on disk failed).",
|
||||||
|
audioStream.getClass(), e);
|
||||||
|
}
|
||||||
|
url = callbackUrl + streamServed.url();
|
||||||
|
streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream));
|
||||||
|
} else {
|
||||||
|
logger.warn("We do not have any callback url, so AllPlay cannot play the audio stream!");
|
||||||
|
tryClose(audioStream);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
String url = convertAudioStreamToUrl(audioStream);
|
|
||||||
handler.playUrl(url);
|
handler.playUrl(url);
|
||||||
} catch (SpeakerException | AllPlayCallbackException e) {
|
} catch (SpeakerException e) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
logger.warn("Unable to play audio stream on speaker {}", getId(), e);
|
logger.warn("Unable to play audio stream on speaker {}", getId(), e);
|
||||||
|
} else {
|
||||||
|
logger.warn("Unable to play audio stream on speaker {}: {}", getId(), e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,36 +137,12 @@ public class AllPlayAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void tryClose(@Nullable InputStream is) {
|
||||||
* Converts the given {@link AudioStream} into an URL which can be used for streaming.
|
if (is != null) {
|
||||||
*
|
try {
|
||||||
* @param audioStream The incoming {@link AudioStream}
|
is.close();
|
||||||
* @return The URL to use for streaming
|
} catch (IOException ignored) {
|
||||||
* @throws AllPlayCallbackException Exception if the URL cannot be created
|
|
||||||
*/
|
|
||||||
private String convertAudioStreamToUrl(AudioStream audioStream) throws AllPlayCallbackException {
|
|
||||||
if (audioStream instanceof URLAudioStream) {
|
|
||||||
// it is an external URL, the speaker can access it itself and play it
|
|
||||||
return ((URLAudioStream) audioStream).getURL();
|
|
||||||
} else {
|
|
||||||
return createUrlForLocalHttpServer(audioStream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createUrlForLocalHttpServer(AudioStream audioStream) throws AllPlayCallbackException {
|
|
||||||
if (callbackUrl != null) {
|
|
||||||
String relativeUrl = audioHTTPServer.serve(audioStream);
|
|
||||||
return callbackUrl + relativeUrl;
|
|
||||||
} else {
|
|
||||||
throw new AllPlayCallbackException("Unable to play audio stream as callback URL is not set");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
private class AllPlayCallbackException extends Exception {
|
|
||||||
|
|
||||||
public AllPlayCallbackException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user