diff --git a/bundles/core/org.eclipse.smarthome.core.audio/OSGI-INF/AudioManager.xml b/bundles/core/org.eclipse.smarthome.core.audio/OSGI-INF/AudioManager.xml index 0ff29a9c375..006068cfa65 100644 --- a/bundles/core/org.eclipse.smarthome.core.audio/OSGI-INF/AudioManager.xml +++ b/bundles/core/org.eclipse.smarthome.core.audio/OSGI-INF/AudioManager.xml @@ -16,7 +16,7 @@ - + diff --git a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/AudioHTTPServer.java b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/AudioHTTPServer.java index 249ef0793a7..57ce45f8fec 100644 --- a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/AudioHTTPServer.java +++ b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/AudioHTTPServer.java @@ -7,8 +7,6 @@ */ package org.eclipse.smarthome.core.audio; -import java.net.URL; - import org.eclipse.smarthome.core.audio.internal.AudioServlet; /** @@ -22,28 +20,28 @@ public interface AudioHTTPServer { /** - * Creates a url for a given {@link AudioStream} where it can be requested a single time. + * Creates a relative url for a given {@link AudioStream} where it can be requested a single time. * Note that the HTTP header only contains "Content-length", if the passed stream is an instance of * {@link FixedLengthAudioStream}. * If the client that requests the url expects this header field to be present, make sure to pass such an instance. * Streams are closed after having been served. * * @param stream the stream to serve on HTTP - * @return the absolute URL to access the stream (using the primary network interface) + * @return the relative URL to access the stream starting with a '/' */ - URL serve(AudioStream stream); + String serve(AudioStream stream); /** - * Creates a url for a given {@link AudioStream} where it can be requested multiple times within the given time - * frame. + * Creates a relative url for a given {@link AudioStream} where it can be requested multiple times within the given + * time frame. * This method only accepts {@link FixedLengthAudioStream}s, since it needs to be able to create multiple concurrent * streams from it, which isn't possible with a regular {@link AudioStream}. * Streams are closed, once they expire. * * @param stream the stream to serve on HTTP * @param seconds number of seconds for which the stream is available through HTTP - * @return the absolute URL to access the stream (using the primary network interface) + * @return the relative URL to access the stream starting with a '/' */ - URL serve(FixedLengthAudioStream stream, int seconds); + String serve(FixedLengthAudioStream stream, int seconds); } diff --git a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java index 5116f1ae056..e4e9a9046cb 100644 --- a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java +++ b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java @@ -9,8 +9,6 @@ import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Hashtable; import java.util.Map; import java.util.UUID; @@ -29,9 +27,6 @@ import org.eclipse.smarthome.core.audio.AudioHTTPServer; import org.eclipse.smarthome.core.audio.AudioStream; import org.eclipse.smarthome.core.audio.FixedLengthAudioStream; -import org.eclipse.smarthome.core.net.HttpServiceUtil; -import org.eclipse.smarthome.core.net.NetUtil; -import org.osgi.framework.BundleContext; import org.osgi.service.http.HttpContext; import org.osgi.service.http.HttpService; import org.osgi.service.http.NamespaceException; @@ -57,15 +52,6 @@ public class AudioServlet extends HttpServlet implements AudioHTTPServer { private Map streamTimeouts = new ConcurrentHashMap<>(); protected HttpService httpService; - private BundleContext bundleContext; - - protected void activate(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - protected void deactivate(BundleContext bundleContext) { - this.bundleContext = null; - } protected void setHttpService(HttpService httpService) { this.httpService = httpService; @@ -179,40 +165,22 @@ private synchronized void removeTimedOutStreams() { } @Override - public URL serve(AudioStream stream) { + public String serve(AudioStream stream) { String streamId = UUID.randomUUID().toString(); oneTimeStreams.put(streamId, stream); - return getURL(streamId); + return getRelativeURL(streamId); } @Override - public URL serve(FixedLengthAudioStream stream, int seconds) { + public String serve(FixedLengthAudioStream stream, int seconds) { String streamId = UUID.randomUUID().toString(); multiTimeStreams.put(streamId, stream); streamTimeouts.put(streamId, System.nanoTime() + TimeUnit.SECONDS.toNanos(seconds)); - return getURL(streamId); + return getRelativeURL(streamId); } - private URL getURL(String streamId) { - try { - final String ipAddress = NetUtil.getLocalIpv4HostAddress(); - if (ipAddress == null) { - logger.warn("No network interface could be found."); - return null; - } - - // we do not use SSL as it can cause certificate validation issues. - final int port = HttpServiceUtil.getHttpServicePort(bundleContext); - if (port == -1) { - logger.warn("Cannot find port of the http service."); - return null; - } - - return new URL("http://" + ipAddress + ":" + port + SERVLET_NAME + "/" + streamId); - } catch (final MalformedURLException e) { - logger.error("Failed to construct audio stream URL: {}", e.getMessage(), e); - return null; - } + private String getRelativeURL(String streamId) { + return SERVLET_NAME + "/" + streamId; } } \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core.voice/OSGI-INF/VoiceManager.xml b/bundles/core/org.eclipse.smarthome.core.voice/OSGI-INF/VoiceManager.xml index 1983752bdb5..5650006e703 100644 --- a/bundles/core/org.eclipse.smarthome.core.voice/OSGI-INF/VoiceManager.xml +++ b/bundles/core/org.eclipse.smarthome.core.voice/OSGI-INF/VoiceManager.xml @@ -20,7 +20,7 @@ - + diff --git a/extensions/binding/org.eclipse.smarthome.binding.sonos/ESH-INF/binding/binding.xml b/extensions/binding/org.eclipse.smarthome.binding.sonos/ESH-INF/binding/binding.xml index fada3b4ca16..4ef1774e302 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.sonos/ESH-INF/binding/binding.xml +++ b/extensions/binding/org.eclipse.smarthome.binding.sonos/ESH-INF/binding/binding.xml @@ -13,7 +13,11 @@ URL for the OPML/tunein.com service false - + + + url to use for playing notification sounds, e.g. http://192.168.0.2:8080 + false + diff --git a/extensions/binding/org.eclipse.smarthome.binding.sonos/META-INF/MANIFEST.MF b/extensions/binding/org.eclipse.smarthome.binding.sonos/META-INF/MANIFEST.MF index b8f66c912b4..0b2b0d0e8e0 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.sonos/META-INF/MANIFEST.MF +++ b/extensions/binding/org.eclipse.smarthome.binding.sonos/META-INF/MANIFEST.MF @@ -15,6 +15,7 @@ Import-Package: com.google.common.collect, org.eclipse.smarthome.core.audio, org.eclipse.smarthome.core.common.registry, org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.net, org.eclipse.smarthome.core.thing, org.eclipse.smarthome.core.thing.binding, org.eclipse.smarthome.core.types, diff --git a/extensions/binding/org.eclipse.smarthome.binding.sonos/OSGI-INF/SonosHandlerFactory.xml b/extensions/binding/org.eclipse.smarthome.binding.sonos/OSGI-INF/SonosHandlerFactory.xml index 51c7092b9b4..efd0f407481 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.sonos/OSGI-INF/SonosHandlerFactory.xml +++ b/extensions/binding/org.eclipse.smarthome.binding.sonos/OSGI-INF/SonosHandlerFactory.xml @@ -8,7 +8,7 @@ http://www.eclipse.org/legal/epl-v10.html --> - + diff --git a/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/handler/ZonePlayerHandler.java b/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/handler/ZonePlayerHandler.java index 1ade866e233..37204dc5a85 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/handler/ZonePlayerHandler.java +++ b/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/handler/ZonePlayerHandler.java @@ -2230,7 +2230,9 @@ private void handleNotificationSound(Command notificationURL, ZonePlayerHandler coordinator.setPositionTrack(notificationPosition); coordinator.play(); waitForFinishedNotification(); - setVolumeForGroup(DecimalType.valueOf(originalVolume)); + if (originalVolume != null) { + setVolumeForGroup(DecimalType.valueOf(originalVolume)); + } coordinator.removeRangeOfTracksFromQueue(new StringType(Integer.toString(notificationPosition) + ",1")); } diff --git a/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosAudioSink.java b/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosAudioSink.java index 6057a27d929..f4715073181 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosAudioSink.java +++ b/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosAudioSink.java @@ -22,6 +22,12 @@ import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.net.HttpServiceUtil; +import org.eclipse.smarthome.core.net.NetUtil; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This makes a Sonos speaker to serve as an {@link AudioSink}- @@ -31,6 +37,8 @@ */ public class SonosAudioSink implements AudioSink { + private final Logger logger = LoggerFactory.getLogger(SonosAudioSink.class); + private static HashSet supportedFormats = new HashSet<>(); static { @@ -40,10 +48,15 @@ public class SonosAudioSink implements AudioSink { private AudioHTTPServer audioHTTPServer; private ZonePlayerHandler handler; + private BundleContext context; + private String callbackUrl; - public SonosAudioSink(ZonePlayerHandler handler, AudioHTTPServer audioHTTPServer) { + public SonosAudioSink(BundleContext context, ZonePlayerHandler handler, AudioHTTPServer audioHTTPServer, + String callbackUrl) { + this.context = context; this.handler = handler; this.audioHTTPServer = audioHTTPServer; + this.callbackUrl = callbackUrl; } @Override @@ -76,12 +89,19 @@ public void process(AudioStream audioStream) throws UnsupportedAudioFormatExcept // FixedLengthAudioStream, but this might be dangerous as we have no clue, how much data to expect from // the stream. } else { - String url = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 10).toString(); + String relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 10).toString(); + String url = createAbsoluteUrl(relativeUrl); + AudioFormat format = audioStream.getFormat(); if (AudioFormat.WAV.isCompatible(format)) { handler.playNotificationSoundURI(new StringType(url + ".wav")); } else if (AudioFormat.MP3.isCompatible(format)) { - handler.playNotificationSoundURI(new StringType(url + ".mp3")); + if (handler.getThing().getStatus() == ThingStatus.ONLINE) { + handler.playNotificationSoundURI(new StringType(url + ".mp3")); + } else { + logger.warn("Sonos speaker '{}' is not online - status is {}", handler.getThing().getUID(), + handler.getThing().getStatus()); + } } else { throw new UnsupportedAudioFormatException("Sonos only supports MP3 or WAV.", format); } @@ -89,6 +109,27 @@ public void process(AudioStream audioStream) throws UnsupportedAudioFormatExcept } } + private String createAbsoluteUrl(String relativeUrl) { + if (callbackUrl != null) { + return callbackUrl + relativeUrl; + } else { + final String ipAddress = NetUtil.getLocalIpv4HostAddress(); + if (ipAddress == null) { + logger.warn("No network interface could be found."); + return null; + } + + // we do not use SSL as it can cause certificate validation issues. + final int port = HttpServiceUtil.getHttpServicePort(context); + if (port == -1) { + logger.warn("Cannot find port of the http service."); + return null; + } + + return "http://" + ipAddress + ":" + port + relativeUrl; + } + } + @Override public Set getSupportedFormats() { return supportedFormats; diff --git a/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosHandlerFactory.java b/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosHandlerFactory.java index ad27cd63ea1..3b6252943a8 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosHandlerFactory.java +++ b/extensions/binding/org.eclipse.smarthome.binding.sonos/src/main/java/org/eclipse/smarthome/binding/sonos/internal/SonosHandlerFactory.java @@ -26,6 +26,7 @@ import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; import org.eclipse.smarthome.core.thing.binding.ThingHandler; import org.eclipse.smarthome.io.transport.upnp.UpnpIOService; +import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; @@ -44,17 +45,23 @@ public class SonosHandlerFactory extends BaseThingHandlerFactory { private UpnpIOService upnpIOService; private DiscoveryServiceRegistry discoveryServiceRegistry; private AudioHTTPServer audioHTTPServer; + private BundleContext context; private Map> audioSinkRegistrations = new ConcurrentHashMap<>(); // optional OPML URL that can be configured through configuration admin private String opmlUrl = null; + // url (scheme+server+port) to use for playing notification sounds + private String callbackUrl = null; + @Override protected void activate(ComponentContext componentContext) { super.activate(componentContext); + this.context = componentContext.getBundleContext(); Dictionary properties = componentContext.getProperties(); opmlUrl = (String) properties.get("opmlUrl"); + callbackUrl = (String) properties.get("callbackUrl"); }; @Override @@ -88,7 +95,7 @@ protected ThingHandler createHandler(Thing thing) { ZonePlayerHandler handler = new ZonePlayerHandler(thing, upnpIOService, discoveryServiceRegistry, opmlUrl); // register the speaker as an audio sink - SonosAudioSink audioSink = new SonosAudioSink(handler, audioHTTPServer); + SonosAudioSink audioSink = new SonosAudioSink(context, handler, audioHTTPServer, callbackUrl); @SuppressWarnings("unchecked") ServiceRegistration reg = (ServiceRegistration) bundleContext .registerService(AudioSink.class.getName(), audioSink, new Hashtable());