gt:     protected void removeAudioSink(AudioSink audioSink) {
context: /**
   * 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.core.audio.internal;
  
  import java.io.File;
  import java.io.IOException;
  import java.net.URI;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  
  import org.eclipse.smarthome.config.core.ConfigConstants;
  import org.eclipse.smarthome.config.core.ConfigOptionProvider;
  import org.eclipse.smarthome.config.core.ParameterOption;
  import org.eclipse.smarthome.core.audio.AudioException;
  import org.eclipse.smarthome.core.audio.AudioManager;
  import org.eclipse.smarthome.core.audio.AudioSink;
  import org.eclipse.smarthome.core.audio.AudioSource;
  import org.eclipse.smarthome.core.audio.AudioStream;
  import org.eclipse.smarthome.core.audio.FileAudioStream;
  import org.eclipse.smarthome.core.audio.URLAudioStream;
  import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
  import org.eclipse.smarthome.core.library.types.PercentType;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * This service provides functionality around audio services and is the central service to be used directly by others.
   *
   * @author Karel Goderis - Initial contribution and API
   * @author Kai Kreuzer - removed unwanted dependencies
   */
  public class AudioManagerImpl implements AudioManager, ConfigOptionProvider {
  
      // constants for the configuration properties
      private static final String CONFIG_URI = "system:audio";
      private static final String CONFIG_DEFAULT_SINK = "defaultSink";
      private static final String CONFIG_DEFAULT_SOURCE = "defaultSource";
  
      private final Logger logger = LoggerFactory.getLogger(AudioManager.class);
  
      // service maps
      private Map<String, AudioSource> audioSources = new ConcurrentHashMap<>();
      private Map<String, AudioSink> audioSinks = new ConcurrentHashMap<>();
  
      /**
       * default settings filled through the service configuration
       */
      private String defaultSource;
      private String defaultSink;
  
      protected void activate(Map<String, Object> config) {
          modified(config);
      }
  
      protected void deactivate() {
      }
  
      protected void modified(Map<String, Object> config) {
          if (config != null) {
              this.defaultSource = config.containsKey(CONFIG_DEFAULT_SOURCE)
                      ? config.get(CONFIG_DEFAULT_SOURCE).toString() : null;
              this.defaultSink = config.containsKey(CONFIG_DEFAULT_SINK) ? config.get(CONFIG_DEFAULT_SINK).toString()
                      : null;
          }
      }
  
      @Override
      public void play(AudioStream audioStream) {
          play(audioStream, null);
      }
  
      @Override
      public void play(AudioStream audioStream, String sinkId) {
          if (audioStream != null) {
              AudioSink sink = getSink(sinkId);
  
              if (sink != null) {
                  try {
                      sink.process(audioStream);
                  } catch (UnsupportedAudioFormatException e) {
                      logger.error("Error playing '{}': {}", audioStream.toString(), e.getMessage());
                  }
              } else {
                  logger.warn("Failed playing audio stream '{}' as no audio sink was found.", audioStream.toString());
              }
          }
      }
  
      @Override
      public void playFile(String fileName) throws AudioException {
          playFile(fileName, null);
      }
  
      @Override
      public void playFile(String fileName, String sink) throws AudioException {
          File file = new File(
                  ConfigConstants.getConfigFolder() + File.separator + SOUND_DIR + File.separator + fileName);
          FileAudioStream is = new FileAudioStream(file);
          play(is, sink);
      }
  
      @Override
      public void stream(String url) throws AudioException {
          stream(url, null);
      }
  
      @Override
      public void stream(String url, String sinkId) throws AudioException {
          AudioStream audioStream = url != null ? new URLAudioStream(url) : null;
          AudioSink sink = getSink(sinkId);
  
          if (sink != null) {
              try {
                  sink.process(audioStream);
              } catch (UnsupportedAudioFormatException e) {
                  logger.error("Error playing '{}': {}", url, e.getMessage());
              }
          }
      }
  
      @Override
      public PercentType getVolume(String sinkId) {
          AudioSink sink = getSink(sinkId);
  
          if (sink != null) {
              try {
                  return sink.getVolume();
              } catch (IOException e) {
                  logger.error("An exception occured while getting the volume of sink {} : '{}'", sink.getId(),
                          e.getMessage());
              }
          }
  
          return PercentType.ZERO;
      }
  
      @Override
      public void setVolume(PercentType volume, String sinkId) {
          AudioSink sink = getSink(sinkId);
  
          if (sink != null) {
              try {
                  sink.setVolume(volume);
              } catch (IOException e) {
                  logger.error("An exception occured while setting the volume of sink {} : '{}'", sink.getId(),
                          e.getMessage());
              }
          }
      }
  
      @Override
      public AudioSource getSource() {
          AudioSource source = null;
          if (defaultSource != null) {
              source = audioSources.get(defaultSource);
              if (source == null) {
                  logger.warn("Default AudioSource service '{}' not available!", defaultSource);
              }
          } else if (!audioSources.isEmpty()) {
              source = audioSources.values().iterator().next();
          } else {
              logger.debug("No AudioSource service available!");
          }
          return source;
      }
  
      @Override
      public AudioSink getSink() {
          AudioSink sink = null;
          if (defaultSink != null) {
              sink = audioSinks.get(defaultSink);
              if (sink == null) {
                  logger.warn("Default AudioSink service '{}' not available!", defaultSink);
              }
          } else if (!audioSinks.isEmpty()) {
              sink = audioSinks.values().iterator().next();
          } else {
              logger.debug("No AudioSink service available!");
          }
          return sink;
      }
  
      @Override
      public Set<String> getSourceIds() {
          return new HashSet<>(audioSources.keySet());
      }
  
      @Override
      public Set<String> getSinkIds() {
          return new HashSet<>(audioSinks.keySet());
      }
  
      @Override
      public Set<String> getSourceIds(String pattern) {
          String regex = pattern.replace("?", ".?").replace("*", ".*?");
          Set<String> matchedSources = new HashSet<String>();
  
          for (String aSource : audioSinks.keySet()) {
              if (aSource.matches(regex)) {
                  matchedSources.add(aSource);
              }
          }
  
          return matchedSources;
      }
  
      @Override
      public AudioSink getSink(String sinkId) {
          AudioSink sink = null;
          if (sinkId == null) {
              sink = getSink();
          } else {
              sink = audioSinks.get(sinkId);
          }
          return sink;
      }
  
      @Override
      public Set<String> getSinks(String pattern) {
          String regex = pattern.replace("?", ".?").replace("*", ".*?");
          Set<String> matchedSinks = new HashSet<String>();
  
          for (String aSink : audioSinks.keySet()) {
              if (aSink.matches(regex)) {
                  matchedSinks.add(aSink);
              }
          }
  
          return matchedSinks;
      }
  
      @Override
      public Collection<ParameterOption> getParameterOptions(URI uri, String param, Locale locale) {
          if (uri.toString().equals(CONFIG_URI)) {
              if (CONFIG_DEFAULT_SOURCE.equals(param)) {
                  List<ParameterOption> options = new ArrayList<>();
                  for (AudioSource source : audioSources.values()) {
                      ParameterOption option = new ParameterOption(source.getId(), source.getLabel(locale));
                      options.add(option);
                  }
                  return options;
              } else if (CONFIG_DEFAULT_SINK.equals(param)) {
                  List<ParameterOption> options = new ArrayList<>();
                  for (AudioSink sink : audioSinks.values()) {
                      ParameterOption option = new ParameterOption(sink.getId(), sink.getLabel(locale));
                      options.add(option);
                  }
                  return options;
              }
          }
          return null;
      }
  
      protected void addAudioSource(AudioSource audioSource) {
          this.audioSources.put(audioSource.getId(), audioSource);
      }
  
      protected void removeAudioSource(AudioSource audioSource) {
          this.audioSources.remove(audioSource.getId());
      }
  
      protected void addAudioSink(AudioSink audioSink) {
          this.audioSinks.put(audioSink.getId(), audioSink);
      }
  
  