Package org.openhab.binding.sonos.internal

Source Code of org.openhab.binding.sonos.internal.SonosZonePlayer$SonosPlayerSubscriptionCallback

/**
* Copyright (c) 2010-2014, openHAB.org and others.
*
* 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.openhab.binding.sonos.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import org.openhab.binding.sonos.SonosCommandType;
import org.openhab.binding.sonos.internal.SonosBinding.SonosZonePlayerState;
import org.openhab.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teleal.cling.UpnpService;
import org.teleal.cling.controlpoint.ActionCallback;
import org.teleal.cling.controlpoint.SubscriptionCallback;
import org.teleal.cling.model.action.ActionArgumentValue;
import org.teleal.cling.model.action.ActionException;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.gena.CancelReason;
import org.teleal.cling.model.gena.GENASubscription;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Action;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.meta.StateVariable;
import org.teleal.cling.model.meta.StateVariableTypeDetails;
import org.teleal.cling.model.state.StateVariableValue;
import org.teleal.cling.model.types.Datatype;
import org.teleal.cling.model.types.InvalidValueException;
import org.teleal.cling.model.types.UDAServiceId;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.model.types.UnsignedIntegerFourBytes;
import org.xml.sax.SAXException;

/**
* Internal data structure which carries the connection details of one Sonos player
* (there could be several)
*
* @author Karel Goderis
* @since 1.1.0
*
*/
class SonosZonePlayer {

  private static Logger logger = LoggerFactory.getLogger(SonosZonePlayer.class);

  protected final int interval = 600;
  private boolean isConfigured = false;

  /** the default socket timeout when requesting an url */
  private static final int SO_TIMEOUT = 5000;

  private RemoteDevice device = null;
  private UDN udn;
  private String id;
  private DateTime lastOPMLQuery;
  private SonosZonePlayerState savedState = null;


  static protected UpnpService upnpService;
  protected SonosBinding sonosBinding;

  private Map<String, StateVariableValue> stateMap = Collections.synchronizedMap(new HashMap<String,StateVariableValue>());

  /**
   * @return the stateMap
   */
  public Map<String, StateVariableValue> getStateMap() {
    return stateMap;
  }

  public boolean isConfigured() {
    return isConfigured;
  }

  SonosZonePlayer(String id, SonosBinding binding) {

    if( binding != null) {
      this.id = id;
      sonosBinding = binding;
   
  }


  private void enableGENASubscriptions(){

    if(device!=null && isConfigured()) {

      // Create a GENA subscription of each service for this device, if supported by the device       
      List<SonosCommandType> subscriptionCommands = SonosCommandType.getSubscriptions();
      List<String> addedSubscriptions = new ArrayList<String>();

      for(SonosCommandType c : subscriptionCommands){
        Service service = device.findService(new UDAServiceId(c.getService()));     
        if(service != null && !addedSubscriptions.contains(c.getService())) {
          SonosPlayerSubscriptionCallback callback = new SonosPlayerSubscriptionCallback(service,interval);
          addedSubscriptions.add(c.getService());
          upnpService.getControlPoint().execute(callback);
        }
      }
    }
  }


  protected boolean isUpdatedValue(String valueName,StateVariableValue newValue) {
    if(newValue != null && valueName != null) {
      StateVariableValue oldValue = stateMap.get(valueName);

      if(newValue.getValue()== null) {
        // we will *not* store an empty value, thank you.
        return false;
      } else {
        if(oldValue == null) {
          // there was nothing stored before
          return true;
        } else {
          if (oldValue.getValue() == null) {
            // something was defined, but no value present
            return true;
          } else {
            if(newValue.getValue().equals(oldValue.getValue())) {
              return false;
            } else {
              return true;
            }
          }
        }
      }
    } 

    return false;
  }



  protected void processStateVariableValue(String valueName,StateVariableValue newValue) {
    if(newValue!=null && isUpdatedValue(valueName,newValue)) {
      Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
      mapToProcess.put(valueName,newValue);
      stateMap.putAll(mapToProcess);
      sonosBinding.processVariableMap(device,mapToProcess);
    }
  }

  /**
   * @return the device
   */
  public RemoteDevice getDevice() {
    return device;
  }

  /**
   * @param device the device to set
   */
  public void setDevice(RemoteDevice device) {
    this.device = device;
    if(upnpService !=null && device!=null) {
      isConfigured = true;
      enableGENASubscriptions();
    }
  }

  public class SonosPlayerSubscriptionCallback extends SubscriptionCallback {


    public SonosPlayerSubscriptionCallback(Service service) {
      super(service);
    }

    public SonosPlayerSubscriptionCallback(Service service,
        int requestedDurationSeconds) {
      super(service, requestedDurationSeconds);
    }

    @Override
    public void established(GENASubscription sub) {
    }

    @Override
    protected void failed(GENASubscription subscription,
        UpnpResponse responseStatus,
        Exception exception,
        String defaultMsg) {
    }

    public void eventReceived(GENASubscription sub) {

      // get the device linked to this service linked to this subscription
      Map<String, StateVariableValue> values = sub.getCurrentValues();       
      Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
      Map<String, StateVariableValue> parsedValues = null;

      // now, lets deal with the specials - some UPNP responses require some XML parsing
      // or we need to update our internal data structure
      // or are things we want to store for further reference

      for(String stateVariable : values.keySet()){
        if(stateVariable.equals("LastChange") && service.getServiceType().getType().equals("AVTransport")){
          try {
            parsedValues = SonosXMLParser.getAVTransportFromXML(values.get(stateVariable).toString());
            for(String someValue : parsedValues.keySet()) {
              //              logger.debug("Lastchange parsed into {}:{}",someValue,parsedValues.get(someValue));
              if(isUpdatedValue(someValue,parsedValues.get(someValue))){
                mapToProcess.put(someValue,parsedValues.get(someValue));
              }
            }
          } catch (SAXException e) {
            logger.error("Could not parse AVTransport from String {}",values.get(stateVariable).toString());
          }

        } else if(stateVariable.equals("LastChange") && service.getServiceType().getType().equals("RenderingControl")){
          try {
            parsedValues = SonosXMLParser.getRenderingControlFromXML(values.get(stateVariable).toString());
            for(String someValue : parsedValues.keySet()) {
              if(isUpdatedValue(someValue,parsedValues.get(someValue))){
                mapToProcess.put(someValue,parsedValues.get(someValue));
              }
            }
          } catch (SAXException e) {
            logger.error("Could not parse RenderingControl from String {}",values.get(stateVariable).toString());
          }
        } else if(isUpdatedValue(stateVariable,values.get(stateVariable))){
          mapToProcess.put(stateVariable, values.get(stateVariable));
        }

      }       

      if(isConfigured) {
        stateMap.putAll(mapToProcess);
        sonosBinding.processVariableMap(device,mapToProcess);
      }
    }

    public void eventsMissed(GENASubscription sub, int numberOfMissedEvents) {
      logger.warn("Missed events: " + numberOfMissedEvents);
    }

    @Override
    protected void ended(GENASubscription subscription,
        CancelReason reason, UpnpResponse responseStatus) {     

      if(device!=null && isConfigured()) {
        //rebooting the GENA subscription
        Service service = subscription.getService();     
        SonosPlayerSubscriptionCallback callback = new SonosPlayerSubscriptionCallback(service,interval);
        upnpService.getControlPoint().execute(callback);

      }
    }
  }

  public void setService(UpnpService service) {
    if(upnpService == null) {
      upnpService = service;
    }
    if(upnpService !=null && device!=null) {
      isConfigured = true;
      enableGENASubscriptions();
    }

  }

  public String getModel() {
    if(device!=null) {
      return device.getDetails().getModelDetails().getModelNumber();
    } else {
      return "Unknown";
    }
  }

  /**
   * @return the udn
   */
  public UDN getUdn() {
    return udn;
  }

  /**
   * @param udn the udn to set
   */
  public void setUdn(UDN udn) {
    this.udn = udn;
  }

  public String getId() {
    return id;
  }


  @Override
  public String toString() {
    return "Sonos [udn=" + udn + ", device=" + device +"]";
  }

  public boolean play() {
    if(isConfigured()) {
      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("Play");
      ActionInvocation invocation = new ActionInvocation(action);

      invocation.setInput("Speed", "1");

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }
  }

  public boolean playRadio(String station){

    if(isConfigured()) {

      List<SonosEntry> stations = getFavoriteRadios();

      SonosEntry theEntry = null;

      // search for the appropriate radio based on its name (title)
      for(SonosEntry someStation : stations){
        if(someStation.getTitle().equals(station)){
          theEntry = someStation;
          break;
        }
      }

      // set the URI of the group coordinator
      if(theEntry != null) {

        SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
        coordinator.setCurrentURI(theEntry);
        coordinator.play();

        return true;
      }
      else {
        return false;
     
    } else {
      return false;
    }

  }

  public boolean playPlayList(String playlist){

    if(isConfigured()) {

      List<SonosEntry> playlists = getPlayLists();

      SonosEntry theEntry = null;

      // search for the appropriate play list based on its name (title)
      for(SonosEntry somePlaylist : playlists){
        if(somePlaylist.getTitle().equals(playlist)){
          theEntry = somePlaylist;
          break;
        }
      }

      // set the URI of the group coordinator
      if(theEntry != null) {

        SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
        //coordinator.setCurrentURI(theEntry);
        coordinator.addURIToQueue(theEntry);

        if(stateMap != null && isConfigured()) {
          StateVariableValue firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued");
          if(firstTrackNumberEnqueued!=null) {
            coordinator.seek("TRACK_NR", firstTrackNumberEnqueued.getValue().toString());
          }
        }

        coordinator.play();

        return true;
      }
      else {
        return false;
     
    } else {
      return false;
    }

  }

  public boolean stop() {
    if(isConfigured()) {
      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("Stop");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }
  }

  public boolean pause() {
    if(isConfigured()) {
      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("Pause");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }
  }

  public boolean next() {
    if(isConfigured()) {
      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("Next");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }
  }

  public boolean previous() {
    if(isConfigured()) {
      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("Previous");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }
  }

  public String getZoneName() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("ZoneName");
      if(value != null) {
        return value.getValue().toString();
      }
    }
    return null;

  }

  public String getZoneGroupID() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("LocalGroupUUID");
      if(value != null) {
        return value.getValue().toString();
      }
    }
    return null;

  }

  public boolean isGroupCoordinator() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("GroupCoordinatorIsLocal");
      if(value != null) {
        return (Boolean) value.getValue();
      }
    }

    return false;

  }

  public SonosZonePlayer getCoordinator(){
    return sonosBinding.getCoordinatorForZonePlayer(this);
  }

  public boolean isCoordinator() {
    return this.equals(getCoordinator());
  }

  public boolean addMember(SonosZonePlayer newMember) {
    if(newMember != null && isConfigured()) {

      SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon:"+udn.getIdentifierString());
      return newMember.setCurrentURI(entry);   

    } else {
      return false;
    }
  }

  public boolean removeMember(SonosZonePlayer oldMember){
    if(oldMember != null && isConfigured()) {

      oldMember.becomeStandAlonePlayer();
      SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon-queue:"+oldMember.getUdn().getIdentifierString()+"#0");
      return oldMember.setCurrentURI(entry);   

    } else {
      return false;
    }
  }

  public boolean becomeStandAlonePlayer() {

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("BecomeCoordinatorOfStandaloneGroup");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }

  }

  public boolean setMute(String string) {
    if(string != null && isConfigured()) {

      Service service = device.findService(new UDAServiceId("RenderingControl"));
      Action action = service.getAction("SetMute");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        invocation.setInput("Channel", "Master");

        if(string.equals("ON") || string.equals("OPEN") || string.equals("UP") ) {
          invocation.setInput("DesiredMute", "True");             
        } else

          if(string.equals("OFF") || string.equals("CLOSED") || string.equals("DOWN") ) {
            invocation.setInput("DesiredMute", "False");             
          } else {
            return false;
          }
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }
      executeActionInvocation(invocation);

      return true;     

    } else {
      return false;
    }
  }

  public String getMute(){
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("MuteMaster");
      if(value != null) {
        return value.getValue().toString();
      }
    }

    return null;

  }

  public boolean setVolume(String value) {
    if(value != null && isConfigured()) {

      Service service = device.findService(new UDAServiceId("RenderingControl"));
      Action action = service.getAction("SetVolume");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        String newValue = value;
        if(value.equals("INCREASE")) {
          int i = Integer.valueOf(this.getVolume());
          newValue = String.valueOf(Math.min(100, i+1));
        } else if (value.equals("DECREASE")) {
          int i = Integer.valueOf(this.getVolume());
          newValue = String.valueOf(Math.max(0, i-1));         
        } else if (value.equals("ON")) {
          newValue = "100";
        } else if (value.equals("OFF")) {
          newValue = "0";
        } else {
          newValue = value;
        }
        invocation.setInput("Channel", "Master");
        invocation.setInput("DesiredVolume",newValue);
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;     

    } else {
      return false;
    }
  }

  public String getVolume() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("VolumeMaster");
      if(value != null) {
        return value.getValue().toString();
      }
    }

    return null;

  }

  public boolean updateTime() {

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("AlarmClock"));
      Action action = service.getAction("GetTimeNow");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true;
    } else {
      return false;
    }

  }

  public String getTime() {
    if(isConfigured()) {
      updateTime();
      if(stateMap != null) {
        StateVariableValue value = stateMap.get("CurrentLocalTime");
        if(value != null) {
          return value.getValue().toString();
        }

      }
    }
    return null;


  }

  protected void executeActionInvocation(ActionInvocation invocation) {
    if(invocation != null) {
      new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();

      ActionException anException = invocation.getFailure();
      if(anException!= null && anException.getMessage()!=null) {
        logger.warn(anException.getMessage());
      }

      Map<String, ActionArgumentValue> result =  invocation.getOutputMap();
      Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
      if(result != null) {

        // only process the variables that have changed value
        for(String variable : result.keySet()) {
          ActionArgumentValue newArgument = result.get(variable);

          StateVariable newVariable = new StateVariable(variable,new StateVariableTypeDetails(newArgument.getDatatype()));
          StateVariableValue newValue = new StateVariableValue(newVariable, newArgument.getValue());

          if(isUpdatedValue(variable,newValue)) {
            mapToProcess.put(variable, newValue);
          }
        }

        stateMap.putAll(mapToProcess);
        sonosBinding.processVariableMap(device,mapToProcess);
      }
    }
  }

  public boolean updateRunningAlarmProperties() {

    if(stateMap != null && isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("GetRunningAlarmProperties");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      // for this property we would like to "compile" a more friendly variable.
      // this newly created "variable" is also store in the stateMap
      StateVariableValue alarmID  = stateMap.get("AlarmID");
      StateVariableValue groupID  = stateMap.get("GroupID");
      StateVariableValue loggedStartTime  = stateMap.get("LoggedStartTime");
      String newStringValue = null;
      if(alarmID != null && loggedStartTime != null) {
        newStringValue = alarmID.getValue() + " - " + loggedStartTime.getValue();
      } else {
        newStringValue = "No running alarm";
      }

      StateVariable newVariable = new StateVariable("RunningAlarmProperties",new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
      StateVariableValue newValue = new StateVariableValue(newVariable, newStringValue);

      processStateVariableValue(newVariable.getName(),newValue);

      return true;
    } else {
      return false;
    }
  }

  public String getRunningAlarmProperties() {
    if(isConfigured()) {
      updateRunningAlarmProperties();
      if(stateMap != null) {
        StateVariableValue value = stateMap.get("RunningAlarmProperties");
        if(value != null) {
          return value.getValue().toString();
        }
      }
    }

    return null;

  }

  public boolean updateZoneInfo() {
    if(stateMap != null && isConfigured()) {
      Service service = device.findService(new UDAServiceId("DeviceProperties"));
      Action action = service.getAction("GetZoneInfo");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      Service anotherservice = device.findService(new UDAServiceId("DeviceProperties"));
      Action anotheraction = service.getAction("GetZoneAttributes");
      ActionInvocation anotherinvocation = new ActionInvocation(anotheraction);

      executeActionInvocation(anotherinvocation);


      //       anotherservice = device.findService(new UDAServiceId("ZoneGroupTopology"));
      //       anotheraction = service.getAction("GetZoneGroupState");
      //       anotherinvocation = new ActionInvocation(anotheraction);

      //      executeActionInvocation(anotherinvocation);

      return true;
    } else {
      return false;
    }
  }

  public String getMACAddress() {
    if(isConfigured()) {
      updateZoneInfo();
      if(stateMap != null) {
        StateVariableValue value = stateMap.get("MACAddress");
        if(value != null) {
          return value.getValue().toString();
        }
      }
    }
    return null;

  }


  public boolean setLed(String string) {

    if(string != null && isConfigured()) {

      Service service = device.findService(new UDAServiceId("DeviceProperties"));
      Action action = service.getAction("SetLEDState");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        if(string.equals("ON") || string.equals("OPEN") || string.equals("UP") ) {
          invocation.setInput("DesiredLEDState", "On");             
        } else

          if(string.equals("OFF") || string.equals("CLOSED") || string.equals("DOWN") ) {
            invocation.setInput("DesiredLEDState", "Off");             
          } else {
            return false;
          }
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }
      executeActionInvocation(invocation);

      return true;     

    } else {
      return false;
    }
  }


  public boolean updateLed() {

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("DeviceProperties"));
      Action action = service.getAction("GetLEDState");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true
    }
    else {
      return false;
    }
  }

  public boolean getLed() {

    if(isConfigured()) {

      updateLed();
      if(stateMap != null) {
        StateVariableValue variable = stateMap.get("CurrentLEDState");
        if(variable != null) {
          return variable.getValue().equals("On") ? true : false;
        }
      }
    } 

    return false;
  }

  public String getCurrentZoneName() {

    if(isConfigured()) {

      updateCurrentZoneName();
      if(stateMap != null) {
        StateVariableValue variable = stateMap.get("CurrentZoneName");
        if(variable != null) {
          return variable.getValue().toString();
        }
      }
    } 

    return null;   
  }

  public boolean updateCurrentZoneName() {

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("DeviceProperties"));
      Action action = service.getAction("GetZoneAttributes");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true
    }
    else {
      return false;
    }

  }

  public boolean updatePosition() {

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("GetPositionInfo");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      return true
    }
    else {
      return false;
    }
  }

  public String getPosition() {
    if(stateMap != null && isConfigured()) {

      updatePosition();
      if(stateMap != null) {
        StateVariableValue variable = stateMap.get("RelTime");
        if(variable != null) {
          return variable.getValue().toString();
        }
      } 
    }
    return null;
  }

  public boolean setPosition(String relTime) {
    return seek("REL_TIME",relTime);
  }

  public boolean setPositionTrack(long tracknr) {
    return seek("TRACK_NR",Long.toString(tracknr));
  }

  protected boolean seek(String unit, String target) {
    if(isConfigured() && unit != null && target != null) {
      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("Seek");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        invocation.setInput("InstanceID","0");
        invocation.setInput("Unit", unit);
        invocation.setInput("Target", target);
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;
    }

    return false;
  }


  public Boolean isLineInConnected() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue statusLineIn = stateMap.get("LineInConnected");
      if(statusLineIn != null) {
        return (Boolean) statusLineIn.getValue();
      }
    }
    return null;

  }

  public Boolean isAlarmRunning() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue status = stateMap.get("AlarmRunning");
      if(status!=null) {
        return status.getValue().equals("1") ? true : false;
      }
    }
    return null;

  }

  public String getTransportState() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue status = stateMap.get("TransportState");
      if(status != null) {
        return status.getValue().toString();
      }
    }

    return null;

  }

  public boolean addURIToQueue(String URI, String meta,int desiredFirstTrack, boolean enqueueAsNext) {

    if(isConfigured() && URI != null && meta != null) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("AddURIToQueue");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        invocation.setInput("InstanceID","0");
        invocation.setInput("EnqueuedURI",URI);
        invocation.setInput("EnqueuedURIMetaData",meta);
        invocation.setInput("DesiredFirstTrackNumberEnqueued",new UnsignedIntegerFourBytes(desiredFirstTrack));
        invocation.setInput("EnqueueAsNext",enqueueAsNext);

      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;
    }

    return false;
  }

  public String getCurrentURI(){
    updateMediaInfo();
    if(stateMap != null && isConfigured()) {
      StateVariableValue status = stateMap.get("CurrentURI");
      if(status != null) {
        return status.getValue().toString();
      }
    }
    return null;

  }

  public long getCurrenTrackNr() {

    if(stateMap != null && isConfigured()) {

      updatePosition();
      if(stateMap != null) {
        StateVariableValue variable = stateMap.get("Track");
        if(variable != null) {
          return ((UnsignedIntegerFourBytes)variable.getValue()).getValue();
        }
      } 
    }
    return (long) -1;
  }


  public boolean updateMediaInfo(){

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("GetMediaInfo");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        invocation.setInput("InstanceID","0");
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;
    }

    return false;

  }

  public boolean addURIToQueue(SonosEntry newEntry) {
    return addURIToQueue(newEntry.getRes(),SonosXMLParser.compileMetadataString(newEntry),1,true);
  }

  public boolean setCurrentURI(String URI, String URIMetaData ) {
    if(URI != null && URIMetaData != null && isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("SetAVTransportURI");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        invocation.setInput("InstanceID","0");
        invocation.setInput("CurrentURI",URI);
        invocation.setInput("CurrentURIMetaData", URIMetaData);
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;     

    } else {
      return false;
    }
  }

  public boolean setCurrentURI(SonosEntry newEntry) {
    return setCurrentURI(newEntry.getRes(),SonosXMLParser.compileMetadataString(newEntry));
  }

  public boolean updateCurrentURIFormatted() {
   
    if(stateMap != null && isConfigured()) {

      String currentURI = null;
      SonosMetaData currentURIMetaData = null;
      SonosMetaData currentTrack = null;

      if(!isGroupCoordinator()) {
        currentURI = getCoordinator().getCurrentURI();
        currentURIMetaData = getCoordinator().getCurrentURIMetadata();
        currentTrack = getCoordinator().getTrackMetadata();

      } else {
        currentURI = getCurrentURI();
        currentURIMetaData = getCurrentURIMetadata();
        currentTrack = getTrackMetadata();
      }

      if(currentURI != null) {

        String resultString = null;
        String artist = null;
        String album = null;
        String title = null;

        if(currentURI.contains("x-sonosapi-stream")) {
          //TODO: Get partner ID for openhab.org

          String stationID = StringUtils.substringBetween(currentURI, ":s", "?sid");

          StateVariable newVariable = new StateVariable("StationID",new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
          StateVariableValue newValue = new StateVariableValue(newVariable, stationID);

          if(this.isUpdatedValue("StationID", newValue) || lastOPMLQuery ==null || lastOPMLQuery.plusMinutes(1).isBeforeNow()) {

            processStateVariableValue(newVariable.getName(),newValue)

            String url = "http://opml.radiotime.com/Describe.ashx?c=nowplaying&id=" + stationID + "&partnerId=IAeIhU42&serial=" + getMACAddress();

            String response = HttpUtil.executeUrl("GET", url, SO_TIMEOUT);
            //logger.debug("RadioTime Response: {}",response);

            lastOPMLQuery = DateTime.now();

            List<String> fields = null;
            try {
              fields = SonosXMLParser.getRadioTimeFromXML(response);
            } catch (SAXException e) {
              logger.error("Could not parse RadioTime from String {}",response);
            }

            resultString = new String();

            if(fields != null && fields.size()>1) {

              artist = fields.get(0);
              title = fields.get(1);
             
              Iterator<String> listIterator = fields.listIterator();
              while(listIterator.hasNext()){
                String field = listIterator.next();
                resultString = resultString + field;
                if(listIterator.hasNext()) {
                  resultString = resultString + " - ";
                }
              }
            }
          } else {
            resultString = stateMap.get("CurrentURIFormatted").getValue().toString();
            title = stateMap.get("CurrentTitle").getValue().toString();
            artist = stateMap.get("CurrentArtist").getValue().toString();
          }


        } else {
          if(currentTrack != null) {
            if(currentTrack.getResource().contains("x-rincon-stream")) {
              title = currentTrack.getTitle();
              album = " ";
              artist = " ";
              resultString = title;
            } else if(!currentTrack.getResource().contains("x-sonosapi-stream")) {
              if (currentTrack.getAlbumArtist().equals("")) {
                resultString = currentTrack.getCreator() + " - " + currentTrack.getAlbum() + " - " + currentTrack.getTitle();
                artist = currentTrack.getCreator();
              } else {
                resultString = currentTrack.getAlbumArtist() + " - " + currentTrack.getAlbum() + " - " + currentTrack.getTitle();
                artist = currentTrack.getAlbumArtist();
              }

              album = currentTrack.getAlbum();
              title = currentTrack.getTitle();
             
              if(album.equals("")) {
                album= " ";
              }

              if(artist.equals("")) {
                artist= " ";
              }
            }
          } else {
            title=" ";
            album = " ";
            artist = " ";
            resultString = " ";
          }
        }

        StateVariable newVariable = new StateVariable("CurrentURIFormatted",new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
        StateVariableValue newValue = new StateVariableValue(newVariable, resultString);
        processStateVariableValue(newVariable.getName(),newValue);   

        // update individual variables
        newVariable = new StateVariable("CurrentArtist",new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));

        if (artist != null) {
          newValue = new StateVariableValue(newVariable, artist);
        } else {
          newValue = new StateVariableValue(newVariable, " ");
        }
        processStateVariableValue(newVariable.getName(), newValue);   

        newVariable = new StateVariable("CurrentTitle",new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
        if (title != null) {
          newValue = new StateVariableValue(newVariable, title);
        } else {
          newValue = new StateVariableValue(newVariable, " ");
        }
        processStateVariableValue(newVariable.getName(), newValue);   

        newVariable = new StateVariable("CurrentAlbum",new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
        if (album != null) {
          newValue = new StateVariableValue(newVariable, album);
        } else {
          newValue = new StateVariableValue(newVariable, " ");
        }
        processStateVariableValue(newVariable.getName(), newValue);   

        return true;


      }
    }

    return false;
  }

  public String getCurrentURIFormatted(){
    updateCurrentURIFormatted();
    if(stateMap != null && isConfigured()) {
      StateVariableValue status = stateMap.get("CurrentURIFormatted");
      if(status != null) {
        return status.getValue().toString();
      }
    }

    return null;

  }

  public String getCurrentURIMetadataAsString() {
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("CurrentTrackMetaData");
      if(value != null) {
        return value.getValue().toString();
      }
    }
    return null;

  }


  public SonosMetaData getCurrentURIMetadata(){
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("CurrentURIMetaData");
      SonosMetaData currentTrack = null;
      if(value != null) {
        try {
          if(((String)value.getValue()).length()!=0) {
            currentTrack = SonosXMLParser.getMetaDataFromXML((String)value.getValue());
          }
        } catch (SAXException e) {
          logger.error("Could not parse MetaData from String {}",value.getValue().toString());
        }
        return currentTrack;
      } else {
        return null;
      }
    } else {
      return null;
    }   
  }

  public SonosMetaData getTrackMetadata(){
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("CurrentTrackMetaData");
      SonosMetaData currentTrack = null;
      if(value != null) {
        try {
          if(((String)value.getValue()).length()!=0) {
            currentTrack = SonosXMLParser.getMetaDataFromXML((String)value.getValue());
          }
        } catch (SAXException e) {
          logger.error("Could not parse MetaData from String {}",value.getValue().toString());
        }
        return currentTrack;
      } else {
        return null;
      }
    } else {
      return null;
    }   
  }

  public SonosMetaData getEnqueuedTransportURIMetaData(){
    if(stateMap != null && isConfigured()) {
      StateVariableValue value = stateMap.get("EnqueuedTransportURIMetaData");
      SonosMetaData currentTrack = null;
      if(value != null) {
        try {
          if(((String)value.getValue()).length()!=0) {
            currentTrack = SonosXMLParser.getMetaDataFromXML((String)value.getValue());
          }
        } catch (SAXException e) {
          logger.error("Could not parse MetaData from String {}",value.getValue().toString());
        }
        return currentTrack;
      } else {
        return null;
      }
    } else {
      return null;
    }   
  }


  public String getCurrentVolume(){
    if(stateMap != null && isConfigured()) {
      StateVariableValue status = stateMap.get("VolumeMaster");
      return status.getValue().toString();
    } else {
      return null;
    }   
  }

  protected List<SonosEntry> getEntries(String type, String filter){

    List<SonosEntry> resultList = null;


    if(isConfigured()) {

      long startAt = 0;

      Service service = device.findService(new UDAServiceId("ContentDirectory"));
      Action action = service.getAction("Browse");
      ActionInvocation invocation = new ActionInvocation(action);
      try {
        invocation.setInput("ObjectID",type);
        invocation.setInput("BrowseFlag","BrowseDirectChildren");
        invocation.setInput("Filter", filter);
        invocation.setInput("StartingIndex",new UnsignedIntegerFourBytes(startAt));
        invocation.setInput("RequestedCount",new UnsignedIntegerFourBytes( 200));
        invocation.setInput("SortCriteria","");
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }
      // Execute this action synchronously
      new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();

      Long totalMatches  = ((UnsignedIntegerFourBytes) invocation.getOutput("TotalMatches").getValue()).getValue();
      Long initialNumberReturned  = ((UnsignedIntegerFourBytes) invocation.getOutput("NumberReturned").getValue()).getValue();
      String initialResult = (String) invocation.getOutput("Result").getValue();

      try {
        resultList = SonosXMLParser.getEntriesFromString(initialResult);
      } catch (SAXException e) {
        logger.error("Could not parse Entries from String {}",initialResult);
      }

      startAt = startAt + initialNumberReturned;

      while(startAt<totalMatches){
        invocation = new ActionInvocation(action);
        try {
          invocation.setInput("ObjectID",type);
          invocation.setInput("BrowseFlag","BrowseDirectChildren");
          invocation.setInput("Filter", filter);
          invocation.setInput("StartingIndex",new UnsignedIntegerFourBytes(startAt));
          invocation.setInput("RequestedCount",new UnsignedIntegerFourBytes( 200));
          invocation.setInput("SortCriteria","");
        } catch (InvalidValueException ex) {
          logger.error("Action Invalid Value Exception {}",ex.getMessage());
        } catch (NumberFormatException ex) {
          logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
        }
        // Execute this action synchronously
        new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();
        String result = (String) invocation.getOutput("Result").getValue();
        int numberReturned  = (Integer) invocation.getOutput("NumberReturned").getValue();

        try {
          resultList.addAll(SonosXMLParser.getEntriesFromString(result));
        } catch (SAXException e) {
          logger.error("Could not parse Entries from String {}",result);
        }
        startAt = startAt + numberReturned;
      }
    }

    return resultList;


  }

  public List<SonosEntry> getArtists( String filter){
    return getEntries("A:",filter);
  }

  public List<SonosEntry> getArtists(){
    return getEntries("A:","dc:title,res,dc:creator,upnp:artist,upnp:album");
  }

  public List<SonosEntry> getAlbums(String filter){
    return getEntries("A:ALBUM",filter);
  }

  public List<SonosEntry> getAlbums(){
    return getEntries("A:ALBUM","dc:title,res,dc:creator,upnp:artist,upnp:album");
  }

  public List<SonosEntry> getTracks( String filter){
    return getEntries("A:TRACKS",filter);
  }

  public List<SonosEntry> getTracks(){
    return getEntries("A:TRACKS","dc:title,res,dc:creator,upnp:artist,upnp:album");
  }

  public List<SonosEntry> getQueue(String filter){
    return getEntries("Q:0",filter);
  }

  public List<SonosEntry> getQueue(){
    return getEntries("Q:0","dc:title,res,dc:creator,upnp:artist,upnp:album");
  }

  public List<SonosEntry> getPlayLists(String filter){
    return getEntries("SQ:",filter);
  }

  public List<SonosEntry> getPlayLists(){
    return getEntries("SQ:","dc:title,res,dc:creator,upnp:artist,upnp:album");
  }

  public List<SonosEntry> getFavoriteRadios(String filter){
    return getEntries("R:0/0",filter);
  }

  public List<SonosEntry> getFavoriteRadios(){
    return getEntries("R:0/0","dc:title,res,dc:creator,upnp:artist,upnp:album");
  }

  public List<SonosAlarm> getCurrentAlarmList(){


    List<SonosAlarm> sonosAlarms = null;

    if(isConfigured()) {

      Service service = device.findService(new UDAServiceId("AlarmClock"));
      Action action = service.getAction("ListAlarms");
      ActionInvocation invocation = new ActionInvocation(action);

      executeActionInvocation(invocation);

      try {
        sonosAlarms = SonosXMLParser.getAlarmsFromStringResult(invocation.getOutput("CurrentAlarmList").toString());
      } catch (SAXException e) {
        logger.error("Could not parse Alarms from String {}",invocation.getOutput("CurrentAlarmList").toString());
      }
    }

    return sonosAlarms;
  }

  public boolean updateAlarm(SonosAlarm alarm) {
    if(alarm != null && isConfigured()) {
      Service service = device.findService(new UDAServiceId("AlarmClock"));
      Action action = service.getAction("ListAlarms");
      ActionInvocation invocation = new ActionInvocation(action);


      DateTimeFormatter formatter = DateTimeFormat.forPattern("HH:mm:ss");

      PeriodFormatter pFormatter= new PeriodFormatterBuilder()
      .printZeroAlways()
      .appendHours()
      .appendSeparator(":")
      .appendMinutes()
      .appendSeparator(":")
      .appendSeconds()
      .toFormatter();


      try {
        invocation.setInput("ID",Integer.toString(alarm.getID()));
        invocation.setInput("StartLocalTime",formatter.print(alarm.getStartTime()));
        invocation.setInput("Duration",pFormatter.print(alarm.getDuration()));
        invocation.setInput("Recurrence",alarm.getRecurrence());
        invocation.setInput("RoomUUID",alarm.getRoomUUID());
        invocation.setInput("ProgramURI",alarm.getProgramURI());
        invocation.setInput("ProgramMetaData",alarm.getProgramMetaData());
        invocation.setInput("PlayMode",alarm.getPlayMode());
        invocation.setInput("Volume",Integer.toString(alarm.getVolume()));
        if(alarm.getIncludeLinkedZones()) {
          invocation.setInput("IncludeLinkedZones","1");
        } else {
          invocation.setInput("IncludeLinkedZones","0");
        }

        if(alarm.getEnabled()) {
          invocation.setInput("Enabled", "1");             
        } else {
          invocation.setInput("Enabled", "0");             
        }
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;
    }
    else {
      return false;
    }

  }

  public boolean setAlarm(String alarmSwitch) {
    if(alarmSwitch.equals("ON") || alarmSwitch.equals("OPEN") || alarmSwitch.equals("UP") ) {
      return setAlarm(true);             
    } else

      if(alarmSwitch.equals("OFF") || alarmSwitch.equals("CLOSED") || alarmSwitch.equals("DOWN") ) {
        return setAlarm(false);           
      } else {
        return false;
      }
  }

  public boolean setAlarm(boolean alarmSwitch) {

    List<SonosAlarm> sonosAlarms = getCurrentAlarmList();

    if(isConfigured()) {

      // find the nearest alarm - take the current time from the Sonos System, not the system where openhab is running

      String currentLocalTime = getTime();
      DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

      DateTime currentDateTime = fmt.parseDateTime(currentLocalTime);

      Duration shortestDuration = Period.days(10).toStandardDuration();
      SonosAlarm firstAlarm = null;

      for(SonosAlarm anAlarm : sonosAlarms) {
        Duration duration = new Duration(currentDateTime,anAlarm.getStartTime());
        if(anAlarm.getStartTime().isBefore(currentDateTime.plus(shortestDuration)) && anAlarm.getRoomUUID().equals(udn.getIdentifierString())) {
          shortestDuration = duration;
          firstAlarm = anAlarm;
        }
      }

      // Set the Alarm
      if(firstAlarm != null) {

        if(alarmSwitch) {
          firstAlarm.setEnabled(true);
        } else {
          firstAlarm.setEnabled(false);
        }

        return updateAlarm(firstAlarm);

      } else {
        return false;
      }
    } else {
      return false;
    }


  }

  public boolean snoozeAlarm(int minutes){
    if(isAlarmRunning() && isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("SnoozeAlarm");
      ActionInvocation invocation = new ActionInvocation(action);

      Period snoozePeriod = Period.minutes(minutes);
      PeriodFormatter pFormatter= new PeriodFormatterBuilder()
      .printZeroAlways()
      .appendHours()
      .appendSeparator(":")
      .appendMinutes()
      .appendSeparator(":")
      .appendSeconds()
      .toFormatter();

      try {
        invocation.setInput("Duration",pFormatter.print(snoozePeriod));
      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }

      executeActionInvocation(invocation);

      return true;

    } else {
      logger.warn("There is no alarm running on {} ",this);
      return false;
    }
  }

  public boolean publicAddress(){

    //check if sourcePlayer has a line-in connected
    if(isLineInConnected() && isConfigured()) {

      //first remove this player from its own group if any
      becomeStandAlonePlayer();

      List<SonosZoneGroup> currentSonosZoneGroups = new ArrayList<SonosZoneGroup>(sonosBinding.getSonosZoneGroups().size());
      for(SonosZoneGroup grp : sonosBinding.getSonosZoneGroups()){
        currentSonosZoneGroups.add((SonosZoneGroup) grp.clone());
      }

      //add all other players to this new group
      for(SonosZoneGroup group : currentSonosZoneGroups){
        for(String player : group.getMembers()){
          SonosZonePlayer somePlayer = sonosBinding.getPlayerForID(player);
          if(somePlayer != this){
            somePlayer.becomeStandAlonePlayer();
            somePlayer.stop();
            addMember(somePlayer);
          }
        }
      }


      //set the URI of the group to the line-in
      //TODO : check if this needs to be set on the group coordinator or can be done on any member
      SonosZonePlayer coordinator = getCoordinator();
      SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon-stream:"+udn.getIdentifierString());
      coordinator.setCurrentURI(entry);
      coordinator.play();

      return true;

    } else {
      logger.warn("Line-in of {} is not connected",this);
      return false;
    }

  }

  public boolean saveQueue(String name, String queueID) {

    if(name != null && queueID != null && isConfigured()) {

      Service service = device.findService(new UDAServiceId("AVTransport"));
      Action action = service.getAction("SaveQueue");
      ActionInvocation invocation = new ActionInvocation(action);

      try {
        invocation.setInput("Title", name);             
        invocation.setInput("ObjectID", queueID);             

      } catch (InvalidValueException ex) {
        logger.error("Action Invalid Value Exception {}",ex.getMessage());
      } catch (NumberFormatException ex) {
        logger.error("Action Invalid Value Format Exception {}",ex.getMessage())
      }
      executeActionInvocation(invocation);

      return true;     

    } else {
      return false;
    }

  }

  /**
   *   Play a given url to music in one of the music libraries.
   *
   *   @param url in the format of //host/folder/filename.mp3
   *   @return true if the url started to play
   */
  public boolean playURI(String url) {
    if (!isConfigured) {
      return false;
    }

    SonosZonePlayer coordinator = sonosBinding
        .getCoordinatorForZonePlayer(this);

    // stop whatever is currently playing
    coordinator.stop();

    // clear any tracks which are pending in the queue
    coordinator.removeAllTracksFromQueue();

    // add the new track we want to play to the queue
    if (!url.startsWith("x-")) {
      // default to file based url
      url = "x-file-cifs:" + url;
    }
    coordinator.addURIToQueue(url, "", 0, true);

    // set the current playlist to our new queue
    coordinator.setCurrentURI("x-rincon-queue:" + udn.getIdentifierString()
        + "#0", "");

    // take the system off mute
    coordinator.setMute("OFF");

    // start jammin'
    return coordinator.play();

  }

  /**
   *   Play music from the line-in of the Player given its name or UDN
   *
   *   @param udn
   *   @return true if the sonos device started to play
   */
  public boolean playLineIn(String remotePlayerName) {
    if (!isConfigured) {
      return false;
    }

    SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
    SonosZonePlayer remotePlayer = sonosBinding.getPlayerForID(remotePlayerName);

    // stop whatever is currently playing
    coordinator.stop();

    // set the
    coordinator.setCurrentURI("x-rincon-stream:" + remotePlayer.getUdn().getIdentifierString(), "");

    // take the system off mute
    coordinator.setMute("OFF");

    // start jammin'
    return coordinator.play();

  }


  /**
   *  Clear all scheduled music from the current queue.
   *
   * @return true if no error occurred.
   */
  public boolean removeAllTracksFromQueue() {

    if (!isConfigured) {
      return false;
    }

    Service service = device.findService(new UDAServiceId("AVTransport"));
    Action action = service.getAction("RemoveAllTracksFromQueue");
    ActionInvocation invocation = new ActionInvocation(action);

    try {
      invocation.setInput("InstanceID", "0");

    } catch (InvalidValueException ex) {
      logger.error("Action Invalid Value Exception {}", ex.getMessage());
    } catch (NumberFormatException ex) {
      logger.error("Action Invalid Value Format Exception {}",
          ex.getMessage());
    }

    executeActionInvocation(invocation);

    return true;

  }

  /**
   *  Save the state (track, position etc) of the Sonos Zone player.
   *
   * @return true if no error occurred.
   */
  protected boolean saveState() {

    synchronized (this) {

      savedState = sonosBinding.new SonosZonePlayerState();
      String currentURI = getCurrentURI();

      if (currentURI != null) {

        if (currentURI.contains("x-sonosapi-stream:")) {
          // we are streaming music
          SonosMetaData track = getTrackMetadata();
          SonosMetaData current = getCurrentURIMetadata();
          if (track != null) {
            savedState.entry = new SonosEntry("",
                current.getTitle(), "", "",
                track.getAlbumArtUri(), "",
                current.getUpnpClass(), currentURI);
          }
        } else if (currentURI.contains("x-rincon:")) {
          // we are a slave to some coordinator
          savedState.entry = new SonosEntry("", "", "", "",
              "", "", "", currentURI);
        } else if (currentURI.contains("x-rincon-stream:")) {
          // we are streaming from the Line In connection
          savedState.entry = new SonosEntry("", "", "", "",
              "", "", "", currentURI);
        } else if (currentURI.contains("x-rincon-queue:")) {
          // we are playing something that sits in the queue
          SonosMetaData queued = getEnqueuedTransportURIMetaData();
          if (queued != null) {

            savedState.track = getCurrenTrackNr();

            if (queued.getUpnpClass().contains(
                "object.container.playlistContainer")) {
              // we are playing a real 'saved' playlist
              List<SonosEntry> playLists = getPlayLists();
              for (SonosEntry someList : playLists) {
                if (someList.getTitle().equals(
                    queued.getTitle())) {
                  savedState.entry = new SonosEntry(
                      someList.getId(),
                      someList.getTitle(),
                      someList.getParentId(), "",
                      "", "",
                      someList.getUpnpClass(),
                      someList.getRes());
                  break;
                }
              }

            } else if (queued.getUpnpClass().contains(
                "object.container")) {
              // we are playing some other sort of
              // 'container' - we will save that to a
              // playlist for our convenience
              logger.debug(
                  "Save State for a container of type {}",
                  queued.getUpnpClass());

              // save the playlist
              String existingList = "";
              List<SonosEntry> playLists = getPlayLists();
              for (SonosEntry someList : playLists) {
                if (someList.getTitle().equals(
                    "openHAB-" + getUdn())) {
                  existingList = someList.getId();
                  break;
                }
              }

              saveQueue(
                  "openHAB-" + getUdn(),
                  existingList);

              // get all the playlists and a ref to our
              // saved list
              playLists = getPlayLists();
              for (SonosEntry someList : playLists) {
                if (someList.getTitle().equals(
                    "openHAB-" + getUdn())) {
                  savedState.entry = new SonosEntry(
                      someList.getId(),
                      someList.getTitle(),
                      someList.getParentId(), "",
                      "", "",
                      someList.getUpnpClass(),
                      someList.getRes());
                  break;
                }
              }

            }
          } else {
            savedState.entry = new SonosEntry("", "", "",
                "", "", "", "", "x-rincon-queue:"
                    + getUdn()
                    .getIdentifierString()
                    + "#0");
          }
        }

        savedState.transportState = getTransportState();
        savedState.volume = getCurrentVolume();
        savedState.relTime = getPosition();
      } else {
        savedState.entry = null;
      }
     
      return true;
    }
  }

  /**
   *  Restore the state (track, position etc) of the Sonos Zone player.
   *
   * @return true if no error occurred.
   */
  protected boolean restoreState() {

    synchronized (this) {
      if (savedState != null) {
        // put settings back
        setVolume(savedState.volume);

        if (isCoordinator()) {
          if (savedState.entry != null) {
            // check if we have a playlist to deal with
            if (savedState.entry
                .getUpnpClass()
                .contains(
                    "object.container.playlistContainer")) {

              addURIToQueue(
                  savedState.entry.getRes(),
                  SonosXMLParser
                  .compileMetadataString(savedState.entry),
                  0, true);
              SonosEntry entry = new SonosEntry(
                  "",
                  "",
                  "",
                  "",
                  "",
                  "",
                  "",
                  "x-rincon-queue:"
                      + getUdn()
                      .getIdentifierString()
                      + "#0");
              setCurrentURI(entry);
              setPositionTrack(savedState.track);

            } else {
              setCurrentURI(savedState.entry);
              setPosition(savedState.relTime);
            }

            if (savedState.transportState
                .equals("PLAYING")) {
              play();
            } else if (savedState.transportState
                .equals("STOPPED")) {
              stop();
            } else if (savedState.transportState
                .equals("PAUSED_PLAYBACK")) {
              pause();
            }
          }
        }
      }
     
      return true;
    }
  }

}
TOP

Related Classes of org.openhab.binding.sonos.internal.SonosZonePlayer$SonosPlayerSubscriptionCallback

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.