import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.Control;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

/**
 * Simple clip player that opens and starts an audio clip.
 */
public class SimpleClipPlayer extends Thread {
	/** Audio clip */
	Clip clip;
	
	/**
	 * Constructor which loads a given audio clip. 
	 * @param clipFile audio file
	 * @param nLoopCount number of times to loop
	 */
	public SimpleClipPlayer(URL clipFile, int nLoopCount) {
		loadAudioFile(clipFile, nLoopCount);
	}
	
	/**
	 * Loads an audio file from the Internet and plays it in a loop
	 * @param clipFile audio file
	 * @param nLoopCount number of times to loop
	 */
	private void loadAudioFile(URL clipFile, int nLoopCount) {
		
		try {			
			// create a stream from the file
			AudioInputStream stream = AudioSystem.getAudioInputStream(clipFile);

			// create info object
			DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat());
			
			// create and open clip
			clip = (Clip)AudioSystem.getLine(info);
			clip.open(stream);
			
			// print supported controls
			this.printSupportedControls();

			// set balance
			this.setPanOrBalance(+1);
			
			// tell the clip to loop
			clip.loop(nLoopCount);
			
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (LineUnavailableException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Prints a list of all supported controls to the command line
	 */
	private void printSupportedControls() {
		// get array of controls of the clip
		Control[] controls = clip.getControls();
		
		// print a list of all controls
		for (Control control : controls) {
			System.out.println(control.getType());
		}
	}
	
	/**
	 * Sets the balance or pan of the audio clip depending on the 
	 * control that is supported (pan for mono, balance for stereo).
	 * @param balance value from -1 (only left) to 1 (only right)
	 */
	private void setPanOrBalance(float balance) {
		// check if balance control is supported
		if(clip.isControlSupported(FloatControl.Type.BALANCE)) {
			
			// get balance control
			FloatControl balanceCtrl = (FloatControl)clip.getControl(FloatControl.Type.BALANCE);
				
			// adjust balance
			balanceCtrl.setValue(balance);
		}
		else {
			System.out.println("No balance control available!");
			// BALANCE control is only for stereo. 
			// The equivalent for mono is PAN, so try PAN:
			if(clip.isControlSupported(FloatControl.Type.PAN)) {

				// get balance control
				FloatControl panCtrl = (FloatControl)clip.getControl(FloatControl.Type.PAN);
					
				// adjust balance
				panCtrl.setValue(balance);
			}
			else {
				System.out.println("No pan control available!");
			}
		}	
	}
	
	/**
	 * Starts the clip and stops it after a given number of loops.
	 */
	public void run(){
		//clip.loop(Clip.LOOP_CONTINUOUSLY);
		
		// start the clip
		clip.start();
		
		// do nothing while clip is active
		while (clip.isActive()){}
		
		// stop the clip
		clip.stop();
	}
	
	/**
	 * Creates a new ClipPlayer object for a specific audio clip.
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			// define URL of the audio clip
			URL url = new URL(
					"http://freewavesamples.com/files/Alesis-Fusion-Bass-Loop.wav"); 
			
			// define the number of times the audio clip should be played 
			int nLoopCount = 3;
			
			// create a new clipPlayer object
			SimpleClipPlayer clipPlayer = new SimpleClipPlayer(url, nLoopCount);
			
			// start clip
			clipPlayer.start();
			
			/* 
			 * JSRessources: 
			 * In the JDK 5.0, the program would exit if we leave the main loop here. 
			 * This is because all Java Sound threads have been changed to be daemon threads, 
			 * not preventing the VM from exiting (this was not the case in 1.4.2 and earlier). 
			 * So we have to stay in a loop to prevent exiting here.
			 */
			while (true) {
				// sleep for 1 second.
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					// Ignore the exception.
				}
			}
			
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}
}

