@Override
  public void run() {

    // format the url for the stream source location (Need to modify for unicast vs multicast)
    // may move to passing streaminfo to music player and forming url there
    String streamURL = "rtp://" + streamInfo.getIpAddress() + ":5005?multicast";

    playSong(streamURL);

    try {
      Thread.sleep(375);
      /*Pausing to ensure the publisher has had time to close in the case of playing the same song twice.
        When playing the same song twice, if the publisher for the first time has not closed, it will not start a
        new stream publisher because the song ID will match one that is still playing.  375 chosen by testing different values
        and this was the shortest.
      */
    } catch (InterruptedException e) {
      // logger.error("",e);
      logger.debug("StreamReceiver thread interrupted on TEARDOWN");
    }

    // Only send the next command if the stream finished playing on its own
    if (state != PlayBack_State.TEARDOWN) {
      callback.notify(Command.NEXT, streamInfo, streamInfo.getZone());
    }

    callback.notifyGUISongProgress(0);

    callback.isStreaming(false);
  }
  @Override
  protected boolean play(Object playInfo) {

    boolean connectedToStream = false;

    if (state == PlayBack_State.INIT) {

      streamInfo = (StreamInfo) playInfo;

      // TODO: Move acknowledgment to after Xuggler is started
      callback.notify(Command.STREAM_ACK, streamInfo, streamInfo.getZone());

      streamThread = new Thread(this);

      streamThread.start();

      connectedToStream = true;

      state = PlayBack_State.PLAYING;
    } else if (state == PlayBack_State.PAUSED) {
      // Send play request to Peer

      connectedToStream = true;

      state = PlayBack_State.PLAYING;

      synchronized (LOCK_OBJECT) {
        LOCK_OBJECT.notify();
      }
    }
    return connectedToStream;
  }
  @Override
  protected boolean pause() {

    boolean streamPaused = false;

    if (state == PlayBack_State.PLAYING) {

      streamPaused = true;

      state = PlayBack_State.PAUSED;
      callback.isStreaming(false);
    }

    return streamPaused;
  }
  @Override
  protected boolean teardown() {

    boolean streamClosed = false;

    // Send teardown request to Peer

    state = PlayBack_State.TEARDOWN;

    alive = false;

    callback.isStreaming(false);

    synchronized (LOCK_OBJECT) {
      LOCK_OBJECT.notifyAll();
    }

    if (streamThread != null && streamThread.isAlive()) {
      streamThread.interrupt();
    }

    return streamClosed;
  }
    public boolean importData(TransferSupport support) {
      if (!canImport(support)) {
        return false;
      }

      Transferable transferable = support.getTransferable();
      try {
        Object draggedImage = transferable.getTransferData(DATA_FLAVOUR);

        JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
        DefaultListModel model = (DefaultListModel) playlist.getModel();
        int dropIndex = dl.getIndex();
        if (model.indexOf(draggedImage) < dropIndex) {
          dropIndex--;
        }
        model.removeElement(draggedImage);
        model.add(dropIndex, draggedImage);
        callback.notify(Command.NEW_QUEUE, Arrays.asList(model.toArray()), gui.getCurrentZone());
        return true;
      } catch (Exception e) {
        e.printStackTrace();
        return false;
      }
    }
  /**
   * The playSong method is responsible for opening a Xuggler container to play song at provided
   * location.
   *
   * @param songURL The location of the song to play (local file path or url)
   */
  public void playSong(String songURL) {

    IContainer container = IContainer.make();

    IContainerFormat format = IContainerFormat.make();

    // Stream format must currently be mp3
    format.setInputFormat("mp3");

    //		int s = container.setInputBufferLength(6270);
    //
    //		if(s < 0){
    //			logger.warn("Input buffer was not set to desired length");
    //		}

    // Probe size value must be >50 for some reason. Native libraries throw an exception if it's
    // <50. Measured in bytes.
    if (container.setProperty("probesize", 50) < 0) {
      logger.warn("Probe size not set for input container.");
    }

    if (container.setProperty("analyzeduration", 1) < 0) {
      logger.warn("Analyze duration not changed for input container.");
    }

    container.setFlag(IContainer.Flags.FLAG_NONBLOCK, true);

    if (container.open(songURL, Type.READ, format, true, false) < 0) {
      throw new IllegalArgumentException("stream not found");
    }

    int numStreams = container.getNumStreams();

    // long streamRec = System.currentTimeMillis();

    logger.info("Number of Audio streams detected {}", numStreams);

    IPacket packet = IPacket.make();
    IStream stream = null;
    IStreamCoder audioCoder = null;

    Map<Integer, IStreamCoder> knownStreams = new HashMap<Integer, IStreamCoder>();

    long previousValue = 0;

    while (container.readNextPacket(packet) >= 0 && alive) {

      if (packet.isComplete()) {

        if (knownStreams.get(packet.getStreamIndex()) == null) {
          container.queryStreamMetaData(); // This method tends to take awhile when reading a stream
          stream = container.getStream(packet.getStreamIndex());
          knownStreams.put(packet.getStreamIndex(), stream.getStreamCoder());

          audioCoder = knownStreams.get(packet.getStreamIndex());

          audioCoder.setTimeBase(stream.getTimeBase());
        }

        if (!audioCoder.isOpen()) {
          if (audioCoder.open(null, null) < 0) {
            throw new RuntimeException("could not open audio decoder for container");
          }

          openSound(audioCoder);

          // System.out.println("Opening sound  " + (System.currentTimeMillis() - streamRec));
        }

        // System.err.println(audioCoder.getNumDroppedFrames());

        int offset = 0;

        IAudioSamples samples = IAudioSamples.make(1024, audioCoder.getChannels());

        while (offset < packet.getSize() && alive) {

          // Wait until the state is playing
          while (state != PlayBack_State.PLAYING) {

            if (state == PlayBack_State.TEARDOWN) {
              break;
            } else {
              try {
                synchronized (LOCK_OBJECT) {
                  // mLine.drain();
                  mLine.flush();
                  mLine.stop();

                  LOCK_OBJECT.wait();

                  mLine.start();
                }
              } catch (InterruptedException e) {
                logger.error("", e);
              }
            }
          }

          int bytesDecoded = audioCoder.decodeAudio(samples, packet, offset);

          if (bytesDecoded < 0) {
            logger.warn("Error occurred decoding audio");
            break;
            // throw new RuntimeException("got error decoding audio");
          }

          offset += bytesDecoded;

          if (samples.isComplete() && alive) {
            playJavaSound(samples);
          }

          // Send the time stamp to the GUI for updating the progress bar
          long newValue = (long) (packet.getTimeStamp() * packet.getTimeBase().getValue());

          // Update GUI every second that the stream is playing
          if (newValue > previousValue) {
            callback.notifyGUISongProgress(newValue);
            callback.isStreaming(true);
            previousValue = newValue;

            if (newValue == streamInfo.getSongDuration()) {
              alive = false;
            }
          }
        }
      }
    }

    closeJavaSound();

    if (audioCoder != null) {
      audioCoder.close();
      audioCoder = null;
    }
    if (container != null) {
      container.close();
      container = null;
    }
  }