martedì 10 giugno 2014

Implementing a seekbar for the audio player

Following the tutorial regarding the mp3 player, let's now focus on how to implement a simple seek bar for skipping music to a certain time. The perfect component for this might seem to be JSlider, however this component listener action is called each time the value of the slider is changed, we don't want to do this, mostly because the value is changed during playing. Let's start making our Seekbar class by extending a JProgressBar then.

public class SeekBar extends JProgressBar {

 private int updatedValue = 0; //sharing between different scopes

 /**
  * Update SeekBar position
  * @param progress in microseconds
  * @param totalVal in seconds
  */
 public void updateSeekBar(long progress, float totalVal)
 {
  BackgroundExecutor.get().execute(new UpdatingTask(progress, totalVal)); //Another thread will calculate the relative position
  setValue(updatedValue);
 }
 
 /**
  * Task used for updating the seek value in another thread.
  * @author Pierluigi
  */
 private class UpdatingTask implements Runnable {

  long progress; float totalVal;
  public UpdatingTask(long progress, float totalVal) {
   this.progress = progress;
   this.totalVal = totalVal;
  }
  
  @Override
  public void run() {
   int lp = (int) (progress / 1000); //progress comes in microseconds
   int seekLenght = getMaximum();
   int n = (int) ((lp/(totalVal*1000))*seekLenght); 
   updatedValue = lastSeekVal+n; 
  }
 }
 ///////////////////////////////////////////////////////////
 
 /**
  * New Constructor, sets a mouseListener
  * (extends JProgressBar)
  */
 public SeekBar()
 {
  super();
  setMaximum(10000); //it's smoother this way
  addMouseListener(new MouseListener() {
   
   @Override
   public void mouseReleased(MouseEvent e) {
   }
   
   @Override
   public void mousePressed(MouseEvent e) {
    float val =  ((float)e.getX()/getWidth()) * getMaximum();
    returnValueToPlayer(val);
    setValue((int)val);
    log("SeekBar pressed: " + val + " x: " + e.getX());
       
   }
   
   @Override
   public void mouseExited(MouseEvent e) {
   }
   
   @Override
   public void mouseEntered(MouseEvent e) {
   }
   
   @Override
   public void mouseClicked(MouseEvent e) {
   }
  });
 }
 
 /**
  * Informs the player about the relative value selected in the seekbar 
  * @throws BasicPlayerException 
  */
 private void returnValueToPlayer(float val){
  //TODO inform our player
 }

 private void log(String str)
 {
  System.out.println("SeekBar] " +str);
 }
}

As you can see I've used a simple MouseListener to implement what the a slider have native, by clicking in a certain x relative to the component I can set a correct value thanks to the power of proportions.
Let's recall that we don't want the swing single thread do math, even when it's this easy, for this we call a new Task executed by one background executor, since I want to have a central use of this class, I've implemented the following singleton:

/**
 * Using this for computing outside swing UI thread
 * @author Pierluigi
 *
 */
public class BackgroundExecutor {
 private static ExecutorService backgroundEx = Executors.newCachedThreadPool(); //UI thread shouldn't do math
 
 public BackgroundExecutor(){}
 
 public static ExecutorService get() { return backgroundEx;}
}

For more information about this see how a thread executor works. Now we can add and test our seekbar, let's add it into init() method from the MainView class like every simple other component.
SeekBar seekbar = new SeekBar();
...
//SeekBar
seekbar.setBounds(5, 10, _W-15, 10);
container.add(seekbar);