From 791d2bc0f08828d164b1d61527071894c0adcc01 Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Mon, 19 Jun 2023 08:40:34 +0200 Subject: [PATCH] [onkyo] Support for more audio streams through the HTTP audio servlet Related to #15113 Signed-off-by: Laurent Garnier --- .../onkyo/internal/OnkyoHandlerFactory.java | 15 ++- .../internal/handler/OnkyoAudioSink.java | 117 ++++++++++++++++++ .../onkyo/internal/handler/OnkyoHandler.java | 9 +- ...SinkHandler.java => OnkyoUpnpHandler.java} | 98 ++------------- 4 files changed, 135 insertions(+), 104 deletions(-) create mode 100644 bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoAudioSink.java rename bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/{UpnpAudioSinkHandler.java => OnkyoUpnpHandler.java} (54%) diff --git a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/OnkyoHandlerFactory.java b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/OnkyoHandlerFactory.java index 6e055571eea7e..e79dc7deb599e 100644 --- a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/OnkyoHandlerFactory.java +++ b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/OnkyoHandlerFactory.java @@ -19,6 +19,7 @@ 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 @@ protected ThingHandler createHandler(Thing thing) { 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 reg = (ServiceRegistration) bundleContext - .registerService(AudioSink.class.getName(), handler, new Hashtable<>()); - audioSinkRegistrations.put(thing.getUID().toString(), reg); - } + OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, stateDescriptionProvider); + OnkyoAudioSink audiSink = new OnkyoAudioSink(handler, audioHTTPServer, callbackUrl); + @SuppressWarnings("unchecked") + ServiceRegistration reg = (ServiceRegistration) bundleContext + .registerService(AudioSink.class.getName(), audiSink, 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 index 0000000000000..8da5154cfbe3f --- /dev/null +++ b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoAudioSink.java @@ -0,0 +1,117 @@ +/** + * 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.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 SUPPORTED_FORMATS = Set.of(AudioFormat.WAV, AudioFormat.MP3); + private static final Set> 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 getSupportedFormats() { + return SUPPORTED_FORMATS; + } + + @Override + public Set> 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; + } + + if (audioStream instanceof URLAudioStream urlAudioStream) { + // it is an external URL, the speaker can access it itself and play it. + handler.playMedia(urlAudioStream.getURL()); + } else if (callbackUrl != null) { + StreamServed streamServed; + try { + // we serve it on our own HTTP server + streamServed = audioHTTPServer.serve(audioStream, 20, true); + } catch (IOException e) { + throw new UnsupportedAudioStreamException("Onkyo can only handle clonable audio streams.", + audioStream.getClass(), e); + } + streamServed.playEnd().thenRun(() -> this.playbackFinished(audioStream)); + handler.playMedia(callbackUrl + streamServed.url()); + } else { + logger.warn("We do not have any callback url, so Onkyo cannot play the audio stream!"); + } + try { + audioStream.close(); + } catch (IOException e) { + } + } + + @Override + public PercentType getVolume() throws IOException { + return handler.getVolume(); + } + + @Override + public void setVolume(PercentType volume) throws IOException { + handler.setVolume(volume); + } +} diff --git a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoHandler.java b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoHandler.java index a927cceb77fc8..e0dff912c92a1 100644 --- a/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoHandler.java +++ b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoHandler.java @@ -37,7 +37,6 @@ 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 @@ * @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 @@ private PercentType scaleVolumeFromReceiver(DecimalType volume) { 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 PercentType getVolume() throws IOException { 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/UpnpAudioSinkHandler.java b/bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoUpnpHandler.java similarity index 54% rename from bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/UpnpAudioSinkHandler.java rename to bundles/org.openhab.binding.onkyo/src/main/java/org/openhab/binding/onkyo/internal/handler/OnkyoUpnpHandler.java index 9b8b998f41693..c109edd729a4f 100644 --- 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/OnkyoUpnpHandler.java @@ -13,21 +13,9 @@ 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; @@ -38,38 +26,20 @@ 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. + * 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 UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant { +public abstract class OnkyoUpnpHandler extends BaseThingHandler implements UpnpIOParticipant { - private static final Set SUPPORTED_FORMATS = new HashSet<>(); - private static final Set> SUPPORTED_STREAMS = new HashSet<>(); + private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpHandler.class); - 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) { + public OnkyoUpnpHandler(Thing thing, UpnpIOService upnpIOService) { super(thing); - this.audioHTTPServer = audioHTTPServer; - this.callbackUrl = callbackUrl; - if (upnpIOService != null) { - this.service = upnpIOService; - } + this.service = upnpIOService; } protected void handlePlayUri(Command command) { @@ -83,7 +53,7 @@ protected void handlePlayUri(Command command) { } } - private void playMedia(String url) { + public void playMedia(String url) { stop(); removeAllTracksFromQueue(); @@ -96,17 +66,7 @@ private void playMedia(String url) { play(); } - @Override - public Set getSupportedFormats() { - return SUPPORTED_FORMATS; - } - - @Override - public Set> getSupportedStreams() { - return SUPPORTED_STREAMS; - } - - private void stop() { + public void stop() { Map inputs = new HashMap<>(); inputs.put("InstanceID", "0"); @@ -159,48 +119,6 @@ private void setCurrentURI(String uri, String uriMetaData) { } } - @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);