mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-26 15:21:41 +01:00
[sonos] Support for more audio streams through the HTTP audio servlet (#15116)
* [sonos] Audio sink supporting more audio streams Related to #15113 Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
1f78f9ac7b
commit
070de816f3
@ -13,11 +13,9 @@
|
|||||||
package org.openhab.binding.sonos.internal;
|
package org.openhab.binding.sonos.internal;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@ -25,9 +23,10 @@ import org.openhab.binding.sonos.internal.handler.ZonePlayerHandler;
|
|||||||
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.AudioSinkSync;
|
||||||
import org.openhab.core.audio.AudioStream;
|
import org.openhab.core.audio.AudioStream;
|
||||||
import org.openhab.core.audio.FileAudioStream;
|
import org.openhab.core.audio.FileAudioStream;
|
||||||
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;
|
||||||
@ -44,17 +43,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author Kai Kreuzer - Initial contribution and API
|
* @author Kai Kreuzer - Initial contribution and API
|
||||||
* @author Christoph Weitkamp - Added getSupportedStreams() and UnsupportedAudioStreamException
|
* @author Christoph Weitkamp - Added getSupportedStreams() and UnsupportedAudioStreamException
|
||||||
|
* @author Laurent Garnier - Support for more audio streams through the HTTP audio servlet
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SonosAudioSink implements AudioSink {
|
public class SonosAudioSink extends AudioSinkSync {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SonosAudioSink.class);
|
private final Logger logger = LoggerFactory.getLogger(SonosAudioSink.class);
|
||||||
|
|
||||||
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Collections
|
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV);
|
||||||
.unmodifiableSet(Stream.of(AudioFormat.MP3, AudioFormat.WAV).collect(Collectors.toSet()));
|
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Set.of(AudioStream.class);
|
||||||
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Collections
|
|
||||||
.unmodifiableSet(Stream.of(FixedLengthAudioStream.class, URLAudioStream.class).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
private AudioHTTPServer audioHTTPServer;
|
private AudioHTTPServer audioHTTPServer;
|
||||||
private ZonePlayerHandler handler;
|
private ZonePlayerHandler handler;
|
||||||
@ -76,29 +74,76 @@ public class SonosAudioSink implements AudioSink {
|
|||||||
return handler.getThing().getLabel();
|
return handler.getThing().getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<@Nullable Void> processAndComplete(@Nullable AudioStream audioStream) {
|
||||||
|
if (audioStream instanceof URLAudioStream) {
|
||||||
|
// Asynchronous handling for URLAudioStream
|
||||||
|
CompletableFuture<@Nullable Void> completableFuture = new CompletableFuture<@Nullable Void>();
|
||||||
|
try {
|
||||||
|
processAsynchronously(audioStream);
|
||||||
|
} catch (UnsupportedAudioFormatException | UnsupportedAudioStreamException e) {
|
||||||
|
completableFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
return completableFuture;
|
||||||
|
} else {
|
||||||
|
return super.processAndComplete(audioStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(@Nullable AudioStream audioStream)
|
public void process(@Nullable AudioStream audioStream)
|
||||||
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
||||||
if (audioStream == null) {
|
if (audioStream instanceof URLAudioStream) {
|
||||||
// in case the audioStream is null, this should be interpreted as a request to end any currently playing
|
processAsynchronously(audioStream);
|
||||||
// stream.
|
} else {
|
||||||
logger.trace("Stop currently playing stream.");
|
processSynchronously(audioStream);
|
||||||
handler.stopPlaying(OnOffType.ON);
|
}
|
||||||
} else if (audioStream instanceof URLAudioStream) {
|
}
|
||||||
|
|
||||||
|
private void processAsynchronously(@Nullable AudioStream audioStream)
|
||||||
|
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
||||||
|
if (audioStream instanceof URLAudioStream urlAudioStream) {
|
||||||
// it is an external URL, the speaker can access it itself and play it.
|
// it is an external URL, the speaker can access it itself and play it.
|
||||||
URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
|
|
||||||
handler.playURI(new StringType(urlAudioStream.getURL()));
|
handler.playURI(new StringType(urlAudioStream.getURL()));
|
||||||
try {
|
try {
|
||||||
audioStream.close();
|
audioStream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
}
|
}
|
||||||
} else if (audioStream instanceof FixedLengthAudioStream) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processSynchronously(@Nullable AudioStream audioStream)
|
||||||
|
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
|
||||||
|
if (audioStream instanceof URLAudioStream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioStream == null) {
|
||||||
|
// in case the audioStream is null, this should be interpreted as a request to end any currently playing
|
||||||
|
// stream.
|
||||||
|
logger.trace("Stop currently playing stream.");
|
||||||
|
handler.stopPlaying(OnOffType.ON);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// we serve it on our own HTTP server and treat it as a notification
|
// we serve it on our own HTTP server and treat it as a notification
|
||||||
// Note that we have to pass a FixedLengthAudioStream, since Sonos does multiple concurrent requests to
|
// Note that Sonos does multiple concurrent requests to the AudioServlet,
|
||||||
// the AudioServlet, so a one time serving won't work.
|
// so a one time serving won't work.
|
||||||
if (callbackUrl != null) {
|
if (callbackUrl != null) {
|
||||||
String relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 10).toString();
|
StreamServed streamServed;
|
||||||
String url = callbackUrl + relativeUrl;
|
try {
|
||||||
|
streamServed = audioHTTPServer.serve(audioStream, 10, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
try {
|
||||||
|
audioStream.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
}
|
||||||
|
throw new UnsupportedAudioStreamException(
|
||||||
|
"Sonos was not able to handle the audio stream (cache on disk failed).", audioStream.getClass(),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
String url = callbackUrl + streamServed.url();
|
||||||
|
|
||||||
AudioFormat format = audioStream.getFormat();
|
AudioFormat format = audioStream.getFormat();
|
||||||
if (!ThingHandlerHelper.isHandlerInitialized(handler)) {
|
if (!ThingHandlerHelper.isHandlerInitialized(handler)) {
|
||||||
@ -115,17 +160,10 @@ public class SonosAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warn("We do not have any callback url, so Sonos cannot play the audio stream!");
|
logger.warn("We do not have any callback url, so Sonos cannot play the audio stream!");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
audioStream.close();
|
audioStream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
}
|
}
|
||||||
throw new UnsupportedAudioStreamException(
|
|
||||||
"Sonos can only handle FixedLengthAudioStreams and URLAudioStreams.", audioStream.getClass());
|
|
||||||
// Instead of throwing an exception, we could ourselves try to wrap it into a
|
|
||||||
// FixedLengthAudioStream, but this might be dangerous as we have no clue, how much data to expect from
|
|
||||||
// the stream.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user