sabato 21 giugno 2014

Implementing UI callbacks with audio player functionality

In the latest posts we discussed how to create the User Interface of our application and how to wrap all the player audio functionality into one simple AudioPlayer class. Now we are gonna put the two things together, the finishing result should be something that slightly works.

This is the uiBehaviour() procedure we left behind (MainView class).

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
 private void uiBehaviour()
 {
  //File chooser
  fc.setMultiSelectionEnabled(true);
  fc.setFileFilter(new FileFilter() {
   
   @Override
   public String getDescription() {
    return "only supported audio files (mp3, wav)";
   }
   
   @Override
   public boolean accept(File f) {
    if(f.isDirectory())
     return true;
    if(f.getName().endsWith(".mp3"))
     return true;
    if(f.getName().endsWith(".wav"))
     return true;
    return false;
   }
  });
  btnAdd.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent e) {
    int returnVal = fc.showOpenDialog(btnAdd);
    if(returnVal == JFileChooser.APPROVE_OPTION){
     File[] files = fc.getSelectedFiles();
     for(File f : files){
      player.addSong(f.getAbsolutePath());
      songList.addElement(f.getName());
      log("Added file " + f.getName() + " to playlist");
     }
    }
    else{
     log("No file selected");
    }
   }
  });
  //Song List
  jSongList.setModel(songList);
  jSongList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  jSongList.setLayoutOrientation(JList.VERTICAL);
  //Event that triggers at double click
  jSongList.addMouseListener(new MouseAdapter()
  {
   public void mouseClicked(MouseEvent evt){
    JList list = (JList)evt.getSource();
    if(evt.getClickCount() == 2){
     log("Double click detected, moving to selected item.");
     int index = list.locationToIndex(evt.getPoint());
     player.setIndexSong(index);
     try {
      player.play();
     } catch (BasicPlayerException ev) {
      ev.printStackTrace();
     }
    }
   }
  });
  
  //Btn Delete
  btnDel.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent e) {
    //Executed Outside UI Thread
    BackgroundExecutor.get().execute(new Runnable() {
     
     @Override
     public void run() {
      int[] indexes = jSongList.getSelectedIndices();
      int removed = 0;
      for(int i : indexes)
      {
       log("Removed Song ("+(i-removed)+")" + songList.get(i-removed));
       player.removeSong(i-removed);
       songList.remove(i-removed);
       removed++;
      }
     }
    });
   }
  });
  //Play Btn
  btnPlay.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent e) {
    try {
     tooglePlay();
    } catch (BasicPlayerException e1) {
     e1.printStackTrace();
    }
   }
  });
  //Next and Previous btns
  btnNext.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent arg0) {
    try {
     player.nextSong();
     //seekbar.resetLastSeek();
    } catch (BasicPlayerException e) {
     log("Error calling the next song");
     e.printStackTrace();
    }
   }
  });
  
  btnPrev.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent arg0) {
    try {
     player.prvSong();
     //seekbar.resetLastSeek();
    } catch (BasicPlayerException e) {
     log("Error calling the previous song");
     e.printStackTrace();
    }
   }
  });
  //Player related behaviour
  player.addBasicPlayerListener(new BasicPlayerListener() {
   
   @Override
   public void stateUpdated(BasicPlayerEvent event) {
    if(event.getCode() == BasicPlayerEvent.EOM)
    {
     //seekbar.resetLastSeek();
     try {
      player.nextSong();
     } catch (BasicPlayerException e) {
      e.printStackTrace();
     }
     log("EOM event catched, calling next song.");
    }
    if(event.getCode() == BasicPlayerEvent.PAUSED){
     //btnPlay.setText(">");
     btnPlay.setIcon(playIcon);
    }
    if(event.getCode() == BasicPlayerEvent.RESUMED){
     //btnPlay.setText("||");
     btnPlay.setIcon(pauseIcon);
    }
   }
   
   @Override
   public void setController(BasicController arg0) {}
   
   @Override
   public void progress(int bytesread, long microseconds, byte[] pcmdata, Map properties) {
    //we don't want to use microseconds directly because it gets resetted on seeking
    seekbar.updateSeekBar(player.getProgressMicroseconds(), currentAudioDurationSec);
    if(wff != null)
     wff.updateWave(pcmdata);
    if(fdf != null)
     fdf.updateWave(pcmdata);
   }
   
   @Override
   public void opened(Object arg0, Map arg1) {
    //btnPlay.setText("||");
    btnPlay.setIcon(pauseIcon);
    jSongList.setSelectedIndex(player.getIndexSong());
    lblplaying.setText("Now Playing: " + songList.get(player.getIndexSong()));
    currentAudioDurationSec = player.getAudioDurationSeconds();
   }
  });
  
  //Timers Executor / Every 1 Second
  timersExec.scheduleAtFixedRate(new Runnable() {
   
   @Override
   public void run() {
    updateTimers();
    //updatePlayingText();
   }
  }, 0, 1, TimeUnit.SECONDS);
  
  
  titleExec.scheduleAtFixedRate(new Runnable() {
   
   @Override
   public void run() {
    updatePlayingText();
   }
  }, 0, 1, TimeUnit.SECONDS);
  
  //Btn Waveform
  btnShWf.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent arg0) {
    SwingUtilities.invokeLater(new Runnable(){
     @Override
     public void run()
     {
      wff = new WaveformFrame();
      wff.setVisible(true);
     }
    });
   }
  });
  //Btn that show freq diagram
  btnShDi.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent arg0) {
    fdf = new FreqDiagFrame();
    fdf.setVisible(true);
   }
  });
  //Open status window frame
  btnShSt.addActionListener(new ActionListener() {
   
   @Override
   public void actionPerformed(ActionEvent arg0) {
    //stf = new StatusFrame();
    stf.setVisible(true);
   }
  });
  
 }
 
 /**
  * Used by the Play/Pause button
  */
 private void tooglePlay() throws BasicPlayerException
 {
  if(songList.size() == 0)
   return;
  if(!player.isPaused()){
   player.pause();
   //btnPlay.setText(">");
   btnPlay.setIcon(playIcon);
   }
  else{player.play();}
 }

It's a lot of code so let's analyze it in order:

  • File chooser: It's a swing component that can be used to create an interface to select one or multiple files. I've set a file filter to let it see only files ending with .mp3 or .wav.
  • btnAdd will use the File chooser to get the songs the user selected, adding it to the playlist.
  • Song list: contains visual list of the playlist, I've added a behavior so that when a double click occurs it gets the index clicked and plays the selected song.
  • Btn Delete: delete the selected songs from the playlist
  • Play Btn: a simple play/pause toogle button.
  • Btn Next/ Btn Prev: move to next or previous song.
  • Player related behaviour: this is where all the fun things happens. We add a new event listener to the basic player, this way we can have some things done every time a certain event happens regarding the player.
    • We use progress to update the seekbar
    • We use opened to detect if a new song start playing ( a new audio file has been opened by the BasicPlayer class...)
    • With state update we detect if the streaming has been paused, stopped, resumed, etc.
  • Other: something regarding additional features

There are some concept that we already explained in the previous posts, the code shouldn't be that hard but for problems or anything just let me know in the comments.

domenica 15 giugno 2014

Creating the AudioPlayer class for playing music in java

Now that our User Interface it's almost done let's focus on making a class that implements all our player functionality, we'll call this class AudioPlayer. Basically, we are gonna crate a wrapper around a BasicPlayer object, if you don't know basicplayer api, check this page.
BasicPlayer layer is the simple player API of jlGui. These classes are designed to be used in any application that needs simple features (play, stop, pause, resume, seek) to play audio file or stream. It's a high-level API over JavaSound API. 
First off, let's start by defining our class properties, so that our goals are clear, we want to have access to most of this kind of information
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 //Extension of BasicPlayer
 private static AudioPlayer instance = null;
 private ArrayList<String> playlist = new ArrayList<String>();
 private int index = 0;
 private boolean paused = true;
 private boolean opened = false;
 private boolean isSeeking = false;
 
 //Current Audio Properties
 private float audioDurationInSeconds = 0;
 private int audioFrameSize = 0;
 private float audioFrameRate = 0;
 //Stream info/status
 private byte[] cpcmdata;
 private long csms = 0; //Current Song microseconds
 private int lastSeekMs = 0; //Every time we seek, basic player returns microseconds are resetted
 //we need a var to mantain the position we seeked to
Now let's create the constructor, the class it's an extension from BasicPlayer class, since I'm wrapping it. I've used a singleton here because it's pattern sweets the AudioPlayer propose, we'll need and control only one AudioPlayer object.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 public class AudioPlayer extends BasicPlayer{  
      //Want to use a singleton  
      private AudioPlayer() {  
           super();  
           //Wanna give the AudioPlayer class a basic behaviour  
           this.addBasicPlayerListener(new BasicPlayerListener() {  
                @Override  
                public void stateUpdated(BasicPlayerEvent event) {  
                     if(event.getCode() == BasicPlayerEvent.EOM)  
                     {  
                          lastSeekMs = 0;  
                          paused = true; //reset player state  
                          opened = false;  
                          log("EOM event catched, player resetted.");  
                     }  
                     if(event.getCode() == BasicPlayerEvent.SEEKING)  
                          isSeeking = true;  
                     if(event.getCode() == BasicPlayerEvent.SEEKED)  
                          isSeeking = false;  
                }  
                @Override  
                public void setController(BasicController arg0) {  
                     //No need to use this  
                }  
                @Override  
                public void progress(int bytesread, long microseconds, byte[] pcmdata, Map properties) {  
                     csms = microseconds;  
                     cpcmdata = pcmdata;  
                }  
                @Override  
                public void opened(Object stream, Map properties) {  
                     log("Opened event caught");  
                     Object[] e = properties.entrySet().toArray();  
                     Object[] k = properties.keySet().toArray();  
                     String line = "Stream properties:";  
                     for(int i = 0; i<properties.size(); i++){  
                          line += "\n\t" + k[i] + ":" + e[i];  
                     }  
                     log(line);  
                     //Set Audio Properties  
                     File file = new File(playlist.get(index));  
                  long audioFileLength = file.length();  
                     int frameSize = (int) properties.get("mp3.framesize.bytes");  
                  float frameRate = (float) properties.get("mp3.framerate.fps");  
                  audioFrameSize = frameSize;  
                  audioFrameRate = frameRate;  
                  audioDurationInSeconds = (audioFileLength / (frameSize * frameRate));  
                  log("\tframesize " + frameSize + " framerate " + frameRate);  
                  log("\tAudio File lenght in seconds is: " + audioDurationInSeconds);  
                }  
           });  
      }  
      public static AudioPlayer getInstance(){  
           if(instance == null)  
                instance = new AudioPlayer();  
           return instance;  
      }  
      /////////////////////////////////////  
 }  
While creating the object, I gave the player some of it's basic functionality, by adding to him a behaviour, I did this by adding a BasicListener with this.addBasicPlayerListener. Function in the listener are automatically called every one in a while by the BasicPlayer class (callback functions). Now follows the other implemented methods.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
 @Override
 public void play() throws BasicPlayerException {
  if(playlist.size() == 0)
   return;
  if(!paused || !opened){
   File f = new File(playlist.get(index));
   log("Opening file... " + f.getAbsolutePath());
   open(f);
   opened = true;
   super.play();
  }
  if(paused)
   super.resume();
  paused = false;
 }
 
 @Override
 public void pause() throws BasicPlayerException {
  log("Paused");
  paused = true;
  super.pause();
 }
 
 @Override
 public void stop() throws BasicPlayerException {
  paused = false;
  super.stop();
 }
 
 public boolean isPaused(){ return paused; }
 
 public boolean isOpenFile() { return opened; }
 
 public ArrayList<String> getPlaylist(){ return playlist; }
 
 public int getIndexSong(){ return index; }
 
 public void setIndexSong(int index){ this.index = index; lastSeekMs = 0; }
 
 public boolean isSeeking() { return isSeeking; }
 
 /**
  * goes to the next song in playlist and plays it
  * @throws BasicPlayerException
  */
 public void nextSong() throws BasicPlayerException{
  if(playlist.size() == 0)
   return;
  lastSeekMs = 0;
  paused = false;
  index = (index+1)%playlist.size();
  play();
 }
 
 /**
  * goes to the previous song and plays it
  * @throws BasicPlayerException
  */
 public void prvSong() throws BasicPlayerException{
  if(playlist.size() == 0)
   return;
  lastSeekMs = 0;
  paused = false;
  index = (index-1)%playlist.size();
  play();
 }
 
 /**
  * Adds a song to the playlist
  * @param songPath
  */
 public void addSong(String songPath) {
  playlist.add(songPath);
 }
 
 /**
  * Remove a song by index
  * @param index
  */
 public void removeSong(int index) {
  playlist.remove(index);
 }
 /**
  * Remove a song by songPath
  * @param songPath
  */
 public void removeSong(String songPath)
 {
  playlist.remove(songPath);
 }
 
 public byte[] getPcmData(){return cpcmdata;}
 
 public long getProgressMicroseconds(){return csms+lastSeekMs;}
 
 public float getAudioDurationSeconds() {return audioDurationInSeconds;}
 
 public float getAudioFrameRate() { return audioFrameRate; }
 
 public float getAudioFrameSize() { return audioFrameSize; }
 
 /**
  * Remembers what's the last position relative to the playing song
  * when seeking
  */
 public void setLastSeekPositionInMs(int seekMs)
 {
  lastSeekMs = seekMs;
 }
 
 
 /**
  * For logging
  * @param line
  */
 private void log(String line)
 {
  System.out.println("AudioPlayer] " + line);
  MainView.stf.addText("AudioPlayer] " + line);
 }
//End of class
As you have already noticed the code it's not that complex since most of the work is done by BasicPlayer, one of the BasicPlayer class though is that after seeking the returned progress in microsecond of the song is reset, as you can see in the code, this problem has been fixed with the use of one variable.

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);

domenica 1 giugno 2014

Making a simple UI for the audio player

First thing first, let's make a nice and simple user interface for our audio player in java. For making the UI we'll use java Swing libraries. Swing is a single threaded model for building graphical interfaces in java, it became standard with java 5 (I think), you don't need to download external libs if you are using a recent version of java.
Let's say this is our basic idea, we'll start by defining the MainView.
Let's create a MainView class that extends JFrame, we'll define a main method that instantiate the main view of our program by calling SwingUtilities.invokeLater, this ensure that the component is created inside swing single threaded envoirment.
Important: Every action that modifies the state of the UI must be performed into the swing thread, however same thread is responsable for rendering, we'll see later in the tutorial how to let other thread execute medium-large computation that shouldn't be performed by java swing thread.
public class MainView extends JFrame{
 /**
  * Class/Frame constructor
  */
 public MainView()
 {
  init(); //see later implementation
  //initMenu();
  //uiBehaviour(); We'll define it later
 }

 //MAIN
 public static void main(String[] args){
  SwingUtilities.invokeLater(new Runnable(){
   @Override
   public void run()
   {
    MainView mv = new MainView();
    mv.setVisible(true);
   }
  });
 }
 
 private void log(String line)
 {
  System.out.println("UI-Main] " + line);
 }
}

Now let's define our components, let's not think about the seekbar now and discuss it later.

public class MainView extends JFrame{
 //Other
 DefaultListModel songList = new DefaultListModel();
 //Components
 JPanel container = new JPanel();
 JButton btnPlay = new JButton();
 JButton btnAdd = new JButton();
 JButton btnNext = new JButton();
 JButton btnPrev = new JButton();
 JButton btnShSt = new JButton();
 JButton btnShWf = new JButton();
 JButton btnShDi = new JButton();
 JButton btnDel = new JButton();
 JButton btnDelAll = new JButton();
 JMenuBar topMenu = new JMenuBar();
 JList jSongList = new JList(songList);
 JLabel lblplaying = new JLabel();
 JLabel lblst = new JLabel();
 JLabel lblet = new JLabel();
 JFileChooser fc = new JFileChooser();
...
}

Every component has a common and unique behaviour, if it's your first time with java swing, check component page docs.
Now let's see how to build the UI, for this I've created a single use isolated method, components listeners will be added in another one.
/**
  * Init Swing graphics UI 
  */
 private void init()
 {
  //MainView
  setTitle("Music Player - Java - 1.0");
  int _H = 300;
  int _W = 330;
  setSize(_W,_H);
  setLocationRelativeTo(null);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  //setResizable(false);
  //Container
  container.setLayout(null);
  getContentPane().add(container);
  //Buttons
  int btn_h = 35;
  int line1 = 80;
  JPanel contBtns = new JPanel();
  contBtns.setBounds(0, line1, 180, btn_h);
  btnPrev.setText("<<");
  btnPrev.setBounds(0, 0, 50, btn_h);
  btnPlay.setText(">");
  btnPlay.setMnemonic(KeyEvent.VK_SPACE);
  btnPlay.setBounds(0, 0, 50, btn_h);
  btnNext.setText(">>");
  btnNext.setBounds(0, 0, 50, btn_h);
  btnAdd.setText("Add Song");
  btnAdd.setBounds(_W-80,line1,70,btn_h);
  contBtns.add(btnPrev);
  contBtns.add(btnPlay);
  contBtns.add(btnNext);
  container.add(contBtns);
  container.add(btnAdd);
  //Now Playing Panel
  JPanel panelNP = new JPanel();
  panelNP.setLayout(new BoxLayout(panelNP, BoxLayout.PAGE_AXIS));
  panelNP.setToolTipText("Now Playing");
  panelNP.setBorder(BorderFactory.createMatteBorder(1, 0, 2, 0, Color.gray));
  panelNP.setBounds(5, line1-25, _W-15, 20);
  //JLabel lblnp = new JLabel("Now Playing:");
  lblplaying.setText("Now Playing: ");
  lblplaying.setBounds(5, 0, 100, 40);
  //panelNP.add(lblnp);
  panelNP.add(lblplaying);
  container.add(panelNP);
  //SongList
  int h_list = 100;
  //jSongList.setBounds(0, line1+50, _W, h_list);
  JScrollPane listScroller = new JScrollPane(jSongList);
  listScroller.setPreferredSize(new Dimension(_W-10,h_list));
  listScroller.setBounds(0, line1+50, _W-10, h_list);
  container.add(listScroller);
  //container.add(jSongList);
  //2Row Buttons
  int line2 = line1+h_list+50;
  JPanel contBtns2 = new JPanel();
  //contBtns2.setLayout(new BoxLayout(contBtns2, BoxLayout.PAGE_AXIS));
  contBtns2.setBounds(0, line2, 220, 50);
  //contBtns2.setBackground(Color.lightGray);
  btnShSt.setText("STAT");
  btnShWf.setText("ShWf");
  btnShDi.setText("ShDi");
  contBtns2.add(btnShSt);
  contBtns2.add(btnShWf);
  contBtns2.add(btnShDi);
  container.add(contBtns2);
  //DelBtns
  btnDel.setBounds(_W-55, line2+5, 45, 30);
  btnDel.setText("X");
  container.add(btnDel);
  //Labels song time
  JPanel contSlbl = new JPanel();
  contSlbl.setBounds(10, 15, _W-20, 20);
  contSlbl.add(lblst);
  contSlbl.add(lblet);
  lblst.setText("00:00");
  lblst.setBorder(new EmptyBorder(0, 0, 0, 200));
  lblet.setText("00:00");
  container.add(contSlbl);
 }

The code should be easy to read, I've used a JPanel as a general container, then by using other panels and setbounds I've created the UI. Pay a closer look about the JList and JScrollPane components though. Also, take a read here if you want to know more about layouts, by default a new Jpanel creates a BoxLayout (that's how I've made the row of buttons).

Next time we'll see how to add some functionality to our UI.