]> git.basschouten.com Git - openhab-addons.git/commitdiff
[onkyo] Support for more audio streams through the HTTP audio servlet (#15117)
authorlolodomo <lg.hc@free.fr>
Wed, 12 Jul 2023 19:54:04 +0000 (21:54 +0200)
committerGitHub <noreply@github.com>
Wed, 12 Jul 2023 19:54:04 +0000 (21:54 +0200)
* [onkyo] Support for more audio streams through the HTTP audio servlet

Related to #15113

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/OnkyoHandlerFactory.java
bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoAudioSink.java [new file with mode: 0644]
bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoHandler.java
bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoUpnpHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/UpnpAudioSinkHandler.java [deleted file]

index 6e055571eea7ee6f8fa75b0e133e760e963fc902..54278af4c9273a067726e859cbe94abf6d5d75df 100644 (file)
@@ -19,6 +19,7 @@ import java.util.Hashtable;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.openhab.binding.onkyo.internal.handler.OnkyoAudioSink;
 import org.openhab.binding.onkyo.internal.handler.OnkyoHandler;
 import org.openhab.core.audio.AudioHTTPServer;
 import org.openhab.core.audio.AudioSink;
@@ -77,14 +78,12 @@ public class OnkyoHandlerFactory extends BaseThingHandlerFactory {
 
         if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
             String callbackUrl = createCallbackUrl();
-            OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, audioHTTPServer, callbackUrl,
-                    stateDescriptionProvider);
-            if (callbackUrl != null) {
-                @SuppressWarnings("unchecked")
-                ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
-                        .registerService(AudioSink.class.getName(), handler, new Hashtable<>());
-                audioSinkRegistrations.put(thing.getUID().toString(), reg);
-            }
+            OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, stateDescriptionProvider);
+            OnkyoAudioSink audioSink = new OnkyoAudioSink(handler, audioHTTPServer, callbackUrl);
+            @SuppressWarnings("unchecked")
+            ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
+                    .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
+            audioSinkRegistrations.put(thing.getUID().toString(), reg);
             return handler;
         }
 
diff --git a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoAudioSink.java b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoAudioSink.java
new file mode 100644 (file)
index 0000000..cfe93f7
--- /dev/null
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.onkyo.internal.handler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Set;
+
+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.AudioSinkAsync;
+import org.openhab.core.audio.AudioStream;
+import org.openhab.core.audio.StreamServed;
+import org.openhab.core.audio.URLAudioStream;
+import org.openhab.core.audio.UnsupportedAudioFormatException;
+import org.openhab.core.audio.UnsupportedAudioStreamException;
+import org.openhab.core.library.types.PercentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * * The {@link OnkyoAudioSink} implements the AudioSink interface.
+ *
+ * @author Paul Frank - Initial contribution
+ * @author Laurent Garnier - Extracted from UpnpAudioSinkHandler to extend AudioSinkAsync
+ */
+@NonNullByDefault
+public class OnkyoAudioSink extends AudioSinkAsync {
+
+    private final Logger logger = LoggerFactory.getLogger(OnkyoAudioSink.class);
+
+    private static final Set<AudioFormat> SUPPORTED_FORMATS = Set.of(AudioFormat.WAV, AudioFormat.MP3);
+    private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Set.of(AudioStream.class);
+
+    private OnkyoHandler handler;
+    private AudioHTTPServer audioHTTPServer;
+    private @Nullable String callbackUrl;
+
+    public OnkyoAudioSink(OnkyoHandler handler, AudioHTTPServer audioHTTPServer, @Nullable String callbackUrl) {
+        this.handler = handler;
+        this.audioHTTPServer = audioHTTPServer;
+        this.callbackUrl = callbackUrl;
+    }
+
+    @Override
+    public Set<AudioFormat> getSupportedFormats() {
+        return SUPPORTED_FORMATS;
+    }
+
+    @Override
+    public Set<Class<? extends AudioStream>> getSupportedStreams() {
+        return SUPPORTED_STREAMS;
+    }
+
+    @Override
+    public String getId() {
+        return handler.getThing().getUID().toString();
+    }
+
+    @Override
+    public @Nullable String getLabel(@Nullable Locale locale) {
+        return handler.getThing().getLabel();
+    }
+
+    @Override
+    protected void processAsynchronously(@Nullable AudioStream audioStream)
+            throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
+        if (audioStream == null) {
+            handler.stop();
+            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) {
+            // we serve it on our own HTTP server
+            StreamServed streamServed;
+            try {
+                streamServed = audioHTTPServer.serve(audioStream, 10, true);
+            } catch (IOException e) {
+                tryClose(audioStream);
+                throw new UnsupportedAudioStreamException(
+                        "Onkyo 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 Onkyo cannot play the audio stream!");
+            tryClose(audioStream);
+            return;
+        }
+        handler.playMedia(url);
+    }
+
+    private void tryClose(@Nullable InputStream is) {
+        if (is != null) {
+            try {
+                is.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    @Override
+    public PercentType getVolume() throws IOException {
+        return handler.getVolume();
+    }
+
+    @Override
+    public void setVolume(PercentType volume) throws IOException {
+        handler.setVolume(volume);
+    }
+}
index a927cceb77fc81a2f6b3154e72400ef7da0831bc..e0dff912c92a14805e4c9704ac7ef58ab2d04f55 100644 (file)
@@ -37,7 +37,6 @@ import org.openhab.binding.onkyo.internal.automation.modules.OnkyoThingActions;
 import org.openhab.binding.onkyo.internal.config.OnkyoDeviceConfiguration;
 import org.openhab.binding.onkyo.internal.eiscp.EiscpCommand;
 import org.openhab.binding.onkyo.internal.eiscp.EiscpMessage;
-import org.openhab.core.audio.AudioHTTPServer;
 import org.openhab.core.io.net.http.HttpUtil;
 import org.openhab.core.io.transport.upnp.UpnpIOService;
 import org.openhab.core.library.types.DecimalType;
@@ -77,7 +76,7 @@ import org.xml.sax.SAXException;
  * @author Pauli Anttila - lot of refactoring
  * @author Stewart Cossey - add dynamic state description provider
  */
-public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventListener {
+public class OnkyoHandler extends OnkyoUpnpHandler implements OnkyoEventListener {
 
     private final Logger logger = LoggerFactory.getLogger(OnkyoHandler.class);
 
@@ -98,9 +97,9 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList
 
     private static final int NET_USB_ID = 43;
 
-    public OnkyoHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer, String callbackUrl,
+    public OnkyoHandler(Thing thing, UpnpIOService upnpIOService,
             OnkyoStateDescriptionProvider stateDescriptionProvider) {
-        super(thing, upnpIOService, audioHTTPServer, callbackUrl);
+        super(thing, upnpIOService);
         this.stateDescriptionProvider = stateDescriptionProvider;
     }
 
@@ -921,7 +920,6 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList
         return new PercentType(((Double) (volume.intValue() / configuration.volumeScale)).intValue());
     }
 
-    @Override
     public PercentType getVolume() throws IOException {
         if (volumeLevelZone1 instanceof PercentType) {
             return (PercentType) volumeLevelZone1;
@@ -930,7 +928,6 @@ public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventList
         throw new IOException();
     }
 
-    @Override
     public void setVolume(PercentType volume) throws IOException {
         handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, downScaleVolume(volume));
     }
diff --git a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoUpnpHandler.java b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoUpnpHandler.java
new file mode 100644 (file)
index 0000000..c109edd
--- /dev/null
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.onkyo.internal.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
+import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
+import org.openhab.core.io.transport.upnp.UpnpIOService;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OnkyoUpnpHandler} is a base class for ThingHandlers for devices which support UPnP playback.
+ *
+ * @author Paul Frank - Initial contribution
+ * @author Laurent Garnier - Separated into OnkyoUpnpHandler and OnkyoAudioSink
+ */
+public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpIOParticipant {
+
+    private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpHandler.class);
+
+    private UpnpIOService service;
+
+    public OnkyoUpnpHandler(Thing thing, UpnpIOService upnpIOService) {
+        super(thing);
+        this.service = upnpIOService;
+    }
+
+    protected void handlePlayUri(Command command) {
+        if (command != null && command instanceof StringType) {
+            try {
+                playMedia(command.toString());
+
+            } catch (IllegalStateException e) {
+                logger.warn("Cannot play URI ({})", e.getMessage());
+            }
+        }
+    }
+
+    public void playMedia(String url) {
+        stop();
+        removeAllTracksFromQueue();
+
+        if (!url.startsWith("x-") && (!url.startsWith("http"))) {
+            url = "x-file-cifs:" + url;
+        }
+
+        setCurrentURI(url, "");
+
+        play();
+    }
+
+    public void stop() {
+        Map<String, String> inputs = new HashMap<>();
+        inputs.put("InstanceID", "0");
+
+        Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", inputs);
+
+        for (String variable : result.keySet()) {
+            this.onValueReceived(variable, result.get(variable), "AVTransport");
+        }
+    }
+
+    private void play() {
+        Map<String, String> inputs = new HashMap<>();
+        inputs.put("InstanceID", "0");
+        inputs.put("Speed", "1");
+        Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs);
+
+        for (String variable : result.keySet()) {
+            this.onValueReceived(variable, result.get(variable), "AVTransport");
+        }
+    }
+
+    private void removeAllTracksFromQueue() {
+        Map<String, String> inputs = new HashMap<>();
+        inputs.put("InstanceID", "0");
+
+        Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs);
+
+        for (String variable : result.keySet()) {
+            this.onValueReceived(variable, result.get(variable), "AVTransport");
+        }
+    }
+
+    private void setCurrentURI(String uri, String uriMetaData) {
+        if (uri != null && uriMetaData != null) {
+            Map<String, String> inputs = new HashMap<>();
+
+            try {
+                inputs.put("InstanceID", "0");
+                inputs.put("CurrentURI", uri);
+                inputs.put("CurrentURIMetaData", uriMetaData);
+            } catch (NumberFormatException ex) {
+                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
+            }
+
+            Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs);
+
+            for (String variable : result.keySet()) {
+                this.onValueReceived(variable, result.get(variable), "AVTransport");
+            }
+        }
+    }
+
+    @Override
+    public String getUDN() {
+        return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
+    }
+
+    @Override
+    public void onValueReceived(String variable, String value, String service) {
+        logger.debug("received variable {} with value {} from service {}", variable, value, service);
+    }
+
+    @Override
+    public void onServiceSubscribed(String service, boolean succeeded) {
+    }
+
+    @Override
+    public void onStatusChanged(boolean status) {
+    }
+}
diff --git a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/UpnpAudioSinkHandler.java b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/UpnpAudioSinkHandler.java
deleted file mode 100644 (file)
index 9b8b998..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.onkyo.internal.handler;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
-import org.openhab.core.audio.AudioFormat;
-import org.openhab.core.audio.AudioHTTPServer;
-import org.openhab.core.audio.AudioSink;
-import org.openhab.core.audio.AudioStream;
-import org.openhab.core.audio.FixedLengthAudioStream;
-import org.openhab.core.audio.URLAudioStream;
-import org.openhab.core.audio.UnsupportedAudioFormatException;
-import org.openhab.core.audio.UnsupportedAudioStreamException;
-import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
-import org.openhab.core.io.transport.upnp.UpnpIOService;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.types.Command;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * * The {@link UpnpAudioSinkHandler} is a base class for ThingHandlers for devices which support UPnP playback. It
- * implements the AudioSink interface.
- * This will allow to register the derived ThingHandler to be registered as an AudioSink in the framework.
- *
- * @author Paul Frank - Initial contribution
- */
-public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant {
-
-    private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
-    private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>();
-
-    static {
-        SUPPORTED_FORMATS.add(AudioFormat.WAV);
-        SUPPORTED_FORMATS.add(AudioFormat.MP3);
-
-        SUPPORTED_STREAMS.add(AudioStream.class);
-    }
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private AudioHTTPServer audioHTTPServer;
-    private String callbackUrl;
-    private UpnpIOService service;
-
-    public UpnpAudioSinkHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer,
-            String callbackUrl) {
-        super(thing);
-        this.audioHTTPServer = audioHTTPServer;
-        this.callbackUrl = callbackUrl;
-        if (upnpIOService != null) {
-            this.service = upnpIOService;
-        }
-    }
-
-    protected void handlePlayUri(Command command) {
-        if (command != null && command instanceof StringType) {
-            try {
-                playMedia(command.toString());
-
-            } catch (IllegalStateException e) {
-                logger.warn("Cannot play URI ({})", e.getMessage());
-            }
-        }
-    }
-
-    private void playMedia(String url) {
-        stop();
-        removeAllTracksFromQueue();
-
-        if (!url.startsWith("x-") && (!url.startsWith("http"))) {
-            url = "x-file-cifs:" + url;
-        }
-
-        setCurrentURI(url, "");
-
-        play();
-    }
-
-    @Override
-    public Set<AudioFormat> getSupportedFormats() {
-        return SUPPORTED_FORMATS;
-    }
-
-    @Override
-    public Set<Class<? extends AudioStream>> getSupportedStreams() {
-        return SUPPORTED_STREAMS;
-    }
-
-    private void stop() {
-        Map<String, String> inputs = new HashMap<>();
-        inputs.put("InstanceID", "0");
-
-        Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", inputs);
-
-        for (String variable : result.keySet()) {
-            this.onValueReceived(variable, result.get(variable), "AVTransport");
-        }
-    }
-
-    private void play() {
-        Map<String, String> inputs = new HashMap<>();
-        inputs.put("InstanceID", "0");
-        inputs.put("Speed", "1");
-        Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs);
-
-        for (String variable : result.keySet()) {
-            this.onValueReceived(variable, result.get(variable), "AVTransport");
-        }
-    }
-
-    private void removeAllTracksFromQueue() {
-        Map<String, String> inputs = new HashMap<>();
-        inputs.put("InstanceID", "0");
-
-        Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs);
-
-        for (String variable : result.keySet()) {
-            this.onValueReceived(variable, result.get(variable), "AVTransport");
-        }
-    }
-
-    private void setCurrentURI(String uri, String uriMetaData) {
-        if (uri != null && uriMetaData != null) {
-            Map<String, String> inputs = new HashMap<>();
-
-            try {
-                inputs.put("InstanceID", "0");
-                inputs.put("CurrentURI", uri);
-                inputs.put("CurrentURIMetaData", uriMetaData);
-            } catch (NumberFormatException ex) {
-                logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
-            }
-
-            Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs);
-
-            for (String variable : result.keySet()) {
-                this.onValueReceived(variable, result.get(variable), "AVTransport");
-            }
-        }
-    }
-
-    @Override
-    public String getId() {
-        return getThing().getUID().toString();
-    }
-
-    @Override
-    public String getLabel(Locale locale) {
-        return getThing().getLabel();
-    }
-
-    @Override
-    public void process(@Nullable AudioStream audioStream)
-            throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
-        if (audioStream == null) {
-            stop();
-            return;
-        }
-
-        String url = null;
-        if (audioStream instanceof URLAudioStream) {
-            // it is an external URL, the speaker can access it itself and play it.
-            URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
-            url = urlAudioStream.getURL();
-        } else {
-            if (callbackUrl != null) {
-                String relativeUrl;
-                if (audioStream instanceof FixedLengthAudioStream) {
-                    // we serve it on our own HTTP server
-                    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 onkyo cannot play the audio stream!");
-                return;
-            }
-        }
-
-        playMedia(url);
-    }
-
-    @Override
-    public String getUDN() {
-        return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
-    }
-
-    @Override
-    public void onValueReceived(String variable, String value, String service) {
-        logger.debug("received variable {} with value {} from service {}", variable, value, service);
-    }
-
-    @Override
-    public void onServiceSubscribed(String service, boolean succeeded) {
-    }
-
-    @Override
-    public void onStatusChanged(boolean status) {
-    }
-}