Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Changed AudioHTTPServer to return relative urls instead of absolute (#…
Browse files Browse the repository at this point in the history
…2374)

This moves the responsibility of constructing the absolute url to call to the consumer - which is imho the much better approach, as e.g. a consumer like a web browser might request it remotely (from a different network) and thus only it knows which server+port to contact (and with this to avoid any cross origin problems).

- adapted Sonos binding to make relative url absolute
- added configuration parameter for callback url to Sonos binding (if not set, heuristic approach through scanning available network interfaces is taken)
- tested it also successfully with webaudio sink in Paper UI

Signed-off-by: Kai Kreuzer <kai@openhab.org>
  • Loading branch information
kaikreuzer authored and maggu2810 committed Oct 30, 2016
1 parent bff4b10 commit 9f89d52
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<service>
<provide interface="org.eclipse.smarthome.core.audio.AudioManager"/>
</service>
<property name="service.pid" type="String" value="audio"/>
<property name="service.pid" type="String" value="org.eclipse.smarthome.audio"/>
<property name="service.config.description.uri" type="String" value="system:audio"/>
<property name="service.config.label" type="String" value="Audio"/>
<property name="service.config.category" type="String" value="system"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
*/
package org.eclipse.smarthome.core.audio;

import java.net.URL;

import org.eclipse.smarthome.core.audio.internal.AudioServlet;

/**
Expand All @@ -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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -57,15 +52,6 @@ public class AudioServlet extends HttpServlet implements AudioHTTPServer {
private Map<String, Long> 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;
Expand Down Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<service>
<provide interface="org.eclipse.smarthome.core.voice.VoiceManager"/>
</service>
<property name="service.pid" type="String" value="voice"/>
<property name="service.pid" type="String" value="org.eclipse.smarthome.voice"/>
<property name="service.config.description.uri" type="String" value="system:voice"/>
<property name="service.config.label" type="String" value="Voice"/>
<property name="service.config.category" type="String" value="system"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
<description>URL for the OPML/tunein.com service</description>
<required>false</required>
</parameter>

<parameter name="callbackUrl" type="text">
<label>Callback URL</label>
<description>url to use for playing notification sounds, e.g. http://192.168.0.2:8080</description>
<required>false</required>
</parameter>
</config-description>

</binding:binding>
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
http://www.eclipse.org/legal/epl-v10.html
-->
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" immediate="true" name="org.eclipse.smarthome.binding.sonos">
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" immediate="true" name="binding.sonos">

<implementation class="org.eclipse.smarthome.binding.sonos.internal.SonosHandlerFactory"/>
<reference bind="setUpnpIOService" cardinality="1..1" interface="org.eclipse.smarthome.io.transport.upnp.UpnpIOService" name="UpnpIOService" policy="static" unbind="unsetUpnpIOService"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}-
Expand All @@ -31,6 +37,8 @@
*/
public class SonosAudioSink implements AudioSink {

private final Logger logger = LoggerFactory.getLogger(SonosAudioSink.class);

private static HashSet<AudioFormat> supportedFormats = new HashSet<>();

static {
Expand All @@ -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
Expand Down Expand Up @@ -76,19 +89,47 @@ 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);
}
}
}
}

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<AudioFormat> getSupportedFormats() {
return supportedFormats;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,17 +45,23 @@ public class SonosHandlerFactory extends BaseThingHandlerFactory {
private UpnpIOService upnpIOService;
private DiscoveryServiceRegistry discoveryServiceRegistry;
private AudioHTTPServer audioHTTPServer;
private BundleContext context;

private Map<String, ServiceRegistration<AudioSink>> 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<String, Object> properties = componentContext.getProperties();
opmlUrl = (String) properties.get("opmlUrl");
callbackUrl = (String) properties.get("callbackUrl");
};

@Override
Expand Down Expand Up @@ -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<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
.registerService(AudioSink.class.getName(), audioSink, new Hashtable<String, Object>());
Expand Down

0 comments on commit 9f89d52

Please sign in to comment.