// Path: src/com/nixus/raop/speaker/Speaker.java
// public interface Speaker extends Service {
// 
//     /**
//      * Return the number of ms delay for this speaker, between a packet
//      * being written and it being heard
//      */
//     public int getDelay();
// 
//     /**
//      * Return the size of the speaker buffer in bytes.
//      */
//     public int getBufferSize();
// 
//     /**
//      * Return a nice name for the Speaker for display to the user
//      */
//     public String getDisplayName();
// 
//     /**
//      * Return a unique ID for the speaker
//      */
//     int getUniqueId();
// 
//     /**
//      * Returns true if this speaker can have its gain adjusted
//      */
//     public boolean hasGain();
// 
//     /**
//      * Set the gain of this speaker. A value of NaN means "no audio".
//      * If this method fails, set the Error.
//      */
//     public void setGain(float gain);
// 
//     /**
//      * Set the player for the speaker. This method should
//      * call {@link Player#removeSpeaker} on the old player
//      * and {@link Player#addSpeaker} on the new one, and
//      * anyone moving speakers between players should call
//      * this method rather than those methods directly.
//      */
//     public void setPlayer(Player player);
// 
//     /**
//      * Get the Player currently assigned to this speaker
//      */
//     public Player getPlayer();
// 
//     /**
//      * If this speaker has failed for some reason, return the Exception.
//      * Otherwise return null.
//      */
//     public Exception getError();
// 
//     /**
//      * Get the volume adjustment that applies to this speaker (+ve or -ve, 0
//      * for no adjustment
//      */
//     public int getVolumeAdjustment();
// 
//     /**
//      * Set the volume adjustment that applies to this speaker, which should be
//      * be added to the gain for this speaker.
//      */
//     public void setVolumeAdjustment(int volumeadjust);
// 
//     /**
//      * Open the Speaker and start it. Reset any error that may have been set.
//      */
//     public void open(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian);
// 
//     /**
//      * Write data to the speaker. If it fails, set the error
//      */
//     public void write(byte[] buf, int off, int len);
// 
//     /**
//      * Immediately stop the speaker and discard any cached data written to it
//      * If it fails, set the error.
//      */
//     public void flush();
// 
//     /**
//      * Block until the data already written to the speaker completes
//      * If it fails, set the error.
//      */
//     public void drain();
// 
//     /**
//      * Close the speaker, immediately stopping it and discarding any data.
//      * Does not throw exception or fail.
//      */
//     public void close();
// 
//     /**
//      * True after open has been called, false after close has been called
//      */
//     public boolean isOpen();
// 
// }


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.nixus.raop.speaker.Speaker;
//                log.debug("write: head="+head+" ql="+queue.length+" writing "+(len-len1)+" to "+qh+" then "+len1+" to 0");
                System.arraycopy(buf, off, queue, qh, len - len1);
                System.arraycopy(buf, off + len - len1, queue, 0, len1);
            }
            head += len;
            push();
        }
    }

    private void push() {
        long newtail = Long.MAX_VALUE;
        // This loop may not push anything if current amount is less than any speaker
        // requires in it's buffer - possible if write has just finished short buffer
        // at end of song.
        for (Iterator<Map.Entry<Speaker,SpeakerState>> i = speakers.entrySet().iterator();i.hasNext();) {
            Map.Entry<Speaker,SpeakerState> e = i.next();
            Speaker speaker = e.getKey();
            SpeakerState state = e.getValue();
            int len = speaker.getBufferSize();
            int qt;
            if (state.tail == Long.MIN_VALUE) {
                state.tail = Math.max(0, tail - state.bytedelay);
            }
            while ((qt=(int)((state.tail - (maxbytedelay-state.bytedelay)) % queue.length)) >= 0 && len <= (head-state.tail)) {
                int len1 = qt + len - queue.length;
                if (len1 <= 0) {
//                    log.debug("push: head="+head+" tail="+state.tail+" writing "+len+" to "+speaker.getDisplayName()+" from "+qt);
                    speaker.write(queue, qt, len);
                } else {
                    speaker.write(queue, qt, len - len1);
// Path: src/com/nixus/raop/speaker/Speaker.java
// public interface Speaker extends Service {
// 
//     /**
//      * Return the number of ms delay for this speaker, between a packet
//      * being written and it being heard
//      */
//     public int getDelay();
// 
//     /**
//      * Return the size of the speaker buffer in bytes.
//      */
//     public int getBufferSize();
// 
//     /**
//      * Return a nice name for the Speaker for display to the user
//      */
//     public String getDisplayName();
// 
//     /**
//      * Return a unique ID for the speaker
//      */
//     int getUniqueId();
// 
//     /**
//      * Returns true if this speaker can have its gain adjusted
//      */
//     public boolean hasGain();
// 
//     /**
//      * Set the gain of this speaker. A value of NaN means "no audio".
//      * If this method fails, set the Error.
//      */
//     public void setGain(float gain);
// 
//     /**
//      * Set the player for the speaker. This method should
//      * call {@link Player#removeSpeaker} on the old player
//      * and {@link Player#addSpeaker} on the new one, and
//      * anyone moving speakers between players should call
//      * this method rather than those methods directly.
//      */
//     public void setPlayer(Player player);
// 
//     /**
//      * Get the Player currently assigned to this speaker
//      */
//     public Player getPlayer();
// 
//     /**
//      * If this speaker has failed for some reason, return the Exception.
//      * Otherwise return null.
//      */
//     public Exception getError();
// 
//     /**
//      * Get the volume adjustment that applies to this speaker (+ve or -ve, 0
//      * for no adjustment
//      */
//     public int getVolumeAdjustment();
// 
//     /**
//      * Set the volume adjustment that applies to this speaker, which should be
//      * be added to the gain for this speaker.
//      */
//     public void setVolumeAdjustment(int volumeadjust);
// 
//     /**
//      * Open the Speaker and start it. Reset any error that may have been set.
//      */
//     public void open(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian);
// 
//     /**
//      * Write data to the speaker. If it fails, set the error
//      */
//     public void write(byte[] buf, int off, int len);
// 
//     /**
//      * Immediately stop the speaker and discard any cached data written to it
//      * If it fails, set the error.
//      */
//     public void flush();
// 
//     /**
//      * Block until the data already written to the speaker completes
//      * If it fails, set the error.
//      */
//     public void drain();
// 
//     /**
//      * Close the speaker, immediately stopping it and discarding any data.
//      * Does not throw exception or fail.
//      */
//     public void close();
// 
//     /**
//      * True after open has been called, false after close has been called
//      */
//     public boolean isOpen();
// 
// }


// Path: src/com/nixus/raop/player/MultiLine.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.nixus.raop.speaker.Speaker;

//                log.debug("write: head="+head+" ql="+queue.length+" writing "+(len-len1)+" to "+qh+" then "+len1+" to 0");
                System.arraycopy(buf, off, queue, qh, len - len1);
                System.arraycopy(buf, off + len - len1, queue, 0, len1);
            }
            head += len;
            push();
        }
    }

    private void push() {
        long newtail = Long.MAX_VALUE;
        // This loop may not push anything if current amount is less than any speaker
        // requires in it's buffer - possible if write has just finished short buffer
        // at end of song.
        for (Iterator<Map.Entry<Speaker,SpeakerState>> i = speakers.entrySet().iterator();i.hasNext();) {
            Map.Entry<Speaker,SpeakerState> e = i.next();
            Speaker speaker = e.getKey();
            SpeakerState state = e.getValue();
            int len = speaker.getBufferSize();
            int qt;
            if (state.tail == Long.MIN_VALUE) {
                state.tail = Math.max(0, tail - state.bytedelay);
            }
            while ((qt=(int)((state.tail - (maxbytedelay-state.bytedelay)) % queue.length)) >= 0 && len <= (head-state.tail)) {
                int len1 = qt + len - queue.length;
                if (len1 <= 0) {
//                    log.debug("push: head="+head+" tail="+state.tail+" writing "+len+" to "+speaker.getDisplayName()+" from "+qt);
                    speaker.write(queue, qt, len);
                } else {
                    speaker.write(queue, qt, len - len1);
                    speaker.write(queue, 0, len1);