package org.openhab.binding.sonos.internal;
import java.io.IOException;
-import java.util.Collections;
import java.util.Locale;
import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
+import org.openhab.core.audio.AudioSinkSync;
import org.openhab.core.audio.AudioStream;
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.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
*
* @author Kai Kreuzer - Initial contribution and API
* @author Christoph Weitkamp - Added getSupportedStreams() and UnsupportedAudioStreamException
+ * @author Laurent Garnier - Support for more audio streams through the HTTP audio servlet
*
*/
@NonNullByDefault
-public class SonosAudioSink implements AudioSink {
+public class SonosAudioSink extends AudioSinkSync {
private final Logger logger = LoggerFactory.getLogger(SonosAudioSink.class);
- private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Collections
- .unmodifiableSet(Stream.of(AudioFormat.MP3, AudioFormat.WAV).collect(Collectors.toSet()));
- private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Collections
- .unmodifiableSet(Stream.of(FixedLengthAudioStream.class, URLAudioStream.class).collect(Collectors.toSet()));
+ private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV);
+ private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Set.of(AudioStream.class);
private AudioHTTPServer audioHTTPServer;
private ZonePlayerHandler handler;
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
public void process(@Nullable AudioStream audioStream)
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
+ if (audioStream instanceof URLAudioStream) {
+ processAsynchronously(audioStream);
+ } else {
+ processSynchronously(audioStream);
+ }
+ }
+
+ 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.
+ handler.playURI(new StringType(urlAudioStream.getURL()));
+ try {
+ audioStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ @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);
- } else if (audioStream instanceof URLAudioStream) {
- // it is an external URL, the speaker can access it itself and play it.
- URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
- handler.playURI(new StringType(urlAudioStream.getURL()));
+ return;
+ }
+
+ // we serve it on our own HTTP server and treat it as a notification
+ // Note that Sonos does multiple concurrent requests to the AudioServlet,
+ // so a one time serving won't work.
+ if (callbackUrl != null) {
+ StreamServed streamServed;
try {
- audioStream.close();
+ streamServed = audioHTTPServer.serve(audioStream, 10, true);
} catch (IOException e) {
- }
- } else if (audioStream instanceof FixedLengthAudioStream) {
- // 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
- // the AudioServlet, so a one time serving won't work.
- if (callbackUrl != null) {
- String relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 10).toString();
- String url = callbackUrl + relativeUrl;
-
- AudioFormat format = audioStream.getFormat();
- if (!ThingHandlerHelper.isHandlerInitialized(handler)) {
- logger.warn("Sonos speaker '{}' is not initialized - status is {}", handler.getThing().getUID(),
- handler.getThing().getStatus());
- } else if (AudioFormat.WAV.isCompatible(format)) {
- handler.playNotificationSoundURI(
- new StringType(url + AudioStreamUtils.EXTENSION_SEPARATOR + FileAudioStream.WAV_EXTENSION));
- } else if (AudioFormat.MP3.isCompatible(format)) {
- handler.playNotificationSoundURI(
- new StringType(url + AudioStreamUtils.EXTENSION_SEPARATOR + FileAudioStream.MP3_EXTENSION));
- } else {
- throw new UnsupportedAudioFormatException("Sonos only supports MP3 or WAV.", format);
+ 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();
+ if (!ThingHandlerHelper.isHandlerInitialized(handler)) {
+ logger.warn("Sonos speaker '{}' is not initialized - status is {}", handler.getThing().getUID(),
+ handler.getThing().getStatus());
+ } else if (AudioFormat.WAV.isCompatible(format)) {
+ handler.playNotificationSoundURI(
+ new StringType(url + AudioStreamUtils.EXTENSION_SEPARATOR + FileAudioStream.WAV_EXTENSION));
+ } else if (AudioFormat.MP3.isCompatible(format)) {
+ handler.playNotificationSoundURI(
+ new StringType(url + AudioStreamUtils.EXTENSION_SEPARATOR + FileAudioStream.MP3_EXTENSION));
} else {
- logger.warn("We do not have any callback url, so Sonos cannot play the audio stream!");
+ throw new UnsupportedAudioFormatException("Sonos only supports MP3 or WAV.", format);
}
} else {
+ logger.warn("We do not have any callback url, so Sonos cannot play the audio stream!");
try {
audioStream.close();
} 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.
}
}