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

Commit

Permalink
initial contribution of Sonos AudioSink support (#2306)
Browse files Browse the repository at this point in the history
* initial contribution of Sonos AudioSink support

Signed-off-by: Kai Kreuzer <kai@openhab.org>

* fix NPEs

Signed-off-by: Kai Kreuzer <kai@openhab.org>
  • Loading branch information
kaikreuzer authored and maggu2810 committed Oct 24, 2016
1 parent dccb24a commit d87b153
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Import-Package: com.google.common.collect,
org.eclipse.smarthome.binding.sonos.handler,
org.eclipse.smarthome.config.core,
org.eclipse.smarthome.config.discovery,
org.eclipse.smarthome.core.audio,
org.eclipse.smarthome.core.common.registry,
org.eclipse.smarthome.core.library.types,
org.eclipse.smarthome.core.thing,
Expand All @@ -23,11 +24,12 @@ Import-Package: com.google.common.collect,
org.jupnp.model,
org.jupnp.model.meta,
org.jupnp.model.types,
org.osgi.framework,
org.osgi.service.component,
org.slf4j,
org.xml.sax,
org.xml.sax.helpers
Service-Component: OSGI-INF/*
Service-Component: OSGI-INF/*.xml
Export-Package: org.eclipse.smarthome.binding.sonos,
org.eclipse.smarthome.binding.sonos.handler
Bundle-ActivationPolicy: lazy
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
<service>
<provide interface="org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory"/>
</service>
<reference bind="setAudioHTTPServer" cardinality="1..1" interface="org.eclipse.smarthome.core.audio.AudioHTTPServer" name="AudioHTTPServer" policy="static" unbind="unsetAudioHTTPServer"/>
</scr:component>
Original file line number Diff line number Diff line change
Expand Up @@ -1469,7 +1469,11 @@ public PercentType getNotificationSoundVolume() {
new PercentType(new BigDecimal(notificationSoundVolume)));
}
}
return new PercentType(new BigDecimal(notificationSoundVolume));
if (notificationSoundVolume != null) {
return new PercentType(new BigDecimal(notificationSoundVolume));
} else {
return null;
}
}

public void addURIToQueue(String URI, String meta, int desiredFirstTrack, boolean enqueueAsNext) {
Expand Down Expand Up @@ -2280,8 +2284,8 @@ private void waitForFinishedNotification() {
while (System.currentTimeMillis() - playstart < NOTIFICATION_TIMEOUT) {
try {
Thread.sleep(50);
if (!stateMap.get("CurrentTitle").equals(notificationTitle)
|| !stateMap.get("TransportState").equals("PLAYING")) {
if (!notificationTitle.equals(stateMap.get("CurrentTitle"))
|| !"PLAYING".equals(stateMap.get("TransportState"))) {
break;
}
} catch (InterruptedException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2014-2016 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.binding.sonos.internal;

import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

import org.eclipse.smarthome.binding.sonos.handler.ZonePlayerHandler;
import org.eclipse.smarthome.core.audio.AudioFormat;
import org.eclipse.smarthome.core.audio.AudioHTTPServer;
import org.eclipse.smarthome.core.audio.AudioSink;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.FixedLengthAudioStream;
import org.eclipse.smarthome.core.audio.URLAudioStream;
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
import org.eclipse.smarthome.core.library.types.PercentType;
import org.eclipse.smarthome.core.library.types.StringType;

/**
* This makes a Sonos speaker to serve as an {@link AudioSink}-
*
* @author Kai Kreuzer - Initial contribution and API
*
*/
public class SonosAudioSink implements AudioSink {

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

static {
supportedFormats.add(AudioFormat.WAV);
supportedFormats.add(AudioFormat.MP3);
}

private AudioHTTPServer audioHTTPServer;
private ZonePlayerHandler handler;

public SonosAudioSink(ZonePlayerHandler handler, AudioHTTPServer audioHTTPServer) {
this.handler = handler;
this.audioHTTPServer = audioHTTPServer;
}

@Override
public String getId() {
return handler.getThing().getUID().toString();
}

@Override
public String getLabel(Locale locale) {
return handler.getThing().getLabel();
}

@Override
public void process(AudioStream audioStream) throws UnsupportedAudioFormatException {
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()));
try {
audioStream.close();
} catch (IOException e) {
}
} else {
// we serve it on our own HTTP server and treat it as a notification
if (!(audioStream instanceof FixedLengthAudioStream)) {
// 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.
throw new UnsupportedAudioFormatException("Sonos can only handle FixedLengthAudioStreams.", null);
// TODO: 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.
} else {
String url = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 10).toString();
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"));
} else {
throw new UnsupportedAudioFormatException("Sonos only supports MP3 or WAV.", format);
}
}
}
}

@Override
public Set<AudioFormat> getSupportedFormats() {
return supportedFormats;
}

@Override
public PercentType getVolume() {
return handler.getNotificationSoundVolume();
}

@Override
public void setVolume(PercentType volume) {
handler.setNotificationSoundVolume(volume);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,34 @@
*/
package org.eclipse.smarthome.binding.sonos.internal;

import static org.eclipse.smarthome.binding.sonos.SonosBindingConstants.*;
import static org.eclipse.smarthome.binding.sonos.config.ZonePlayerConfiguration.UDN;

import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.smarthome.binding.sonos.SonosBindingConstants;
import org.eclipse.smarthome.binding.sonos.handler.ZonePlayerHandler;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.config.discovery.DiscoveryServiceRegistry;
import org.eclipse.smarthome.core.audio.AudioHTTPServer;
import org.eclipse.smarthome.core.audio.AudioSink;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.ThingUID;
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 com.google.common.collect.Lists;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link SonosHandlerFactory} is responsible for creating things and thing
* handlers.
*
*
* @author Karel Goderis - Initial contribution
*/
public class SonosHandlerFactory extends BaseThingHandlerFactory {
Expand All @@ -40,10 +43,14 @@ public class SonosHandlerFactory extends BaseThingHandlerFactory {

private UpnpIOService upnpIOService;
private DiscoveryServiceRegistry discoveryServiceRegistry;
private AudioHTTPServer audioHTTPServer;

private Map<String, ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();

// optional OPML URL that can be configured through configuration admin
private String opmlUrl = null;

@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
Dictionary<String, Object> properties = componentContext.getProperties();
Expand All @@ -54,7 +61,7 @@ protected void activate(ComponentContext componentContext) {
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
ThingUID bridgeUID) {

if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
if (SonosBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
ThingUID sonosDeviceUID = getPlayerUID(thingTypeUID, thingUID, configuration);
logger.debug("Creating a sonos thing with ID '{}'", sonosDeviceUID);
return super.createThing(thingTypeUID, configuration, sonosDeviceUID, null);
Expand All @@ -66,23 +73,43 @@ public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {

return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
return SonosBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}

@Override
protected ThingHandler createHandler(Thing thing) {

ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
if (SonosBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
logger.debug("Creating a ZonePlayerHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new ZonePlayerHandler(thing, upnpIOService, discoveryServiceRegistry, opmlUrl);

ZonePlayerHandler handler = new ZonePlayerHandler(thing, upnpIOService, discoveryServiceRegistry, opmlUrl);

// register the speaker as an audio sink
SonosAudioSink audioSink = new SonosAudioSink(handler, audioHTTPServer);
@SuppressWarnings("unchecked")
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
.registerService(AudioSink.class.getName(), audioSink, new Hashtable<String, Object>());
audioSinkRegistrations.put(thing.getUID().toString(), reg);

return handler;

}

return null;
}

@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
ServiceRegistration<AudioSink> reg = audioSinkRegistrations.get(thing.getUID().toString());
if (reg != null) {
reg.unregister();
}
}

private ThingUID getPlayerUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) {

String udn = (String) configuration.get(UDN);
Expand Down Expand Up @@ -110,4 +137,12 @@ protected void unsetDiscoveryServiceRegistry(DiscoveryServiceRegistry discoveryS
this.discoveryServiceRegistry = null;
}

protected void setAudioHTTPServer(AudioHTTPServer audioHTTPServer) {
this.audioHTTPServer = audioHTTPServer;
}

protected void unsetAudioHTTPServer(AudioHTTPServer audioHTTPServer) {
this.audioHTTPServer = null;
}

}

0 comments on commit d87b153

Please sign in to comment.