]> git.basschouten.com Git - openhab-addons.git/commitdiff
[sonos] Support for more audio streams through the HTTP audio servlet (#15116)
authorlolodomo <lg.hc@free.fr>
Sun, 2 Jul 2023 09:22:31 +0000 (11:22 +0200)
committerGitHub <noreply@github.com>
Sun, 2 Jul 2023 09:22:31 +0000 (11:22 +0200)
* [sonos] Audio sink supporting more audio streams

Related to #15113

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
bundles/org.openhab.binding.sonos/src/main/java/org/openhab/binding/sonos/internal/SonosAudioSink.java

index 369c4af660b1636552d0d67b52f3ecc78701e6f7..afd9e223c57b5b2a0b463084a9b3cefc577bbbcb 100644 (file)
 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;
@@ -25,9 +23,10 @@ import org.openhab.binding.sonos.internal.handler.ZonePlayerHandler;
 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;
@@ -44,17 +43,16 @@ import org.slf4j.LoggerFactory;
  *
  * @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;
@@ -76,56 +74,96 @@ public class SonosAudioSink implements AudioSink {
         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.
         }
     }