Android Open Source - SimpleMusicPlayer Song Queue






From Project

Back to project page SimpleMusicPlayer.

License

The source code is released under:

Copyright 2011 Micha? Kazior. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: ...

If you think the Android project SimpleMusicPlayer listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.michalkazior.simplemusicplayer;
/*from   w ww  . j  a va2s.  co m*/
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import com.michalkazior.simplemusicplayer.Player.State;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.SeekBar.OnSeekBarChangeListener;

/**
 * Main activity.
 * 
 * The user may manage the list of enqueued songs, play, pause.
 */
public class SongQueue extends Activity {
  private Button playButton, skipButton;
  private TextView songTimeTextView;
  private SeekBar songSeekBar;
  private ListView enqueuedSongsListView;

  private Song selectedSong = null;
  private boolean isEmpty = false;

  private boolean isDraggingSeekBar = false;
  private int seekZoomBegin = 0;
  private int seekZoomLength = 0;
  private Timer seekZoomTimer = new Timer();
  private Timer updateTimer = null;

  private Player player = null;
  private ServiceConnection playerConnection = new ServiceConnection() {
    @Override
    public void onServiceDisconnected(ComponentName name) {
      player = null;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      player = ((Player.Proxy) service).getPlayer();
      player.registerHandler(playerMessenger);
      updateEnqueuedSongs();
      updatePlaying();
    }
  };

  private Messenger playerMessenger = new Messenger(new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch (Player.Event.values()[msg.what]) {
        case EnqueuedSongsChanged:
          updateEnqueuedSongs();
          break;

        case StateChanged:
          enqueuedSongsListView.invalidateViews();
          updatePlaying();
          break;
      }
    }
  });

  private void updateEnqueuedSongs() {
    /*
     * Avoid updating when we display 'no songs enqueued'. It will get
     * updated soon enough.
     * 
     * Also avoiding setupEmptyView() here fixes the case of end of playback
     * which would not switch to 'no songs enqueued' screen.
     */
    if (player.getEnqueuedSongs().length == 0) {
      setupEmptyView();
      return;
    }

    setupContentView();
    ((MainSongAdapter) enqueuedSongsListView.getAdapter()).setItems(player.getEnqueuedSongs());
  }

  private void updateTimerStart() {
    if (updateTimer != null) return;

    updateTimer = new Timer();
    updateTimer.schedule(new TimerTask() {
      @Override
      public void run() {
        SongQueue.this.runOnUiThread(new Runnable() {
          @Override
          public void run() {
            updatePlaying();
          }
        });
      }
    }, 1000, 1000);
  }

  private void updateTimerStop() {
    if (updateTimer == null) return;

    updateTimer.cancel();
    updateTimer.purge();
    updateTimer = null;
  }

  private void updatePlaying() {
    if (isEmpty) {
      updateTimerStop();
      return;
    }

    int duration = player.getDuration();
    int position = player.getPosition();
    State state = player.getState();

    switch (state) {
      case IS_PLAYING:
        updateTimerStart();
        break;
      case IS_STOPPED:
      case IS_PAUSED:
        updateTimerStop();
        break;
    }

    switch (state) {
      case IS_STOPPED:
        updatePosition(0, 0);
        break;
      case IS_PLAYING:
      case IS_PAUSED:
        if (!isDraggingSeekBar) updatePosition(position, duration);
        break;
    }

    switch (state) {
      case IS_STOPPED:
        songTimeTextView.setText("");
        break;
      case IS_PLAYING:
        playButton.setText(R.string.button_pause);
        break;
      case IS_PAUSED:
        playButton.setText(R.string.button_play);
        break;
    }
  }

  private void seekZoomTimerStart() {
    seekZoomTimerStop();

    seekZoomTimer.schedule(new TimerTask() {
      @Override
      public void run() {
        int position = SongQueue.this.songSeekBar.getProgress();
        int duration = SongQueue.this.songSeekBar.getMax();
        int start = position - (duration / 4);
        int length = duration / 2;

        if (start < 0) start = 0;
        if (start + length > duration) length = duration - start;

        seekZoomBegin += start;
        seekZoomLength = length;

        SongQueue.this.runOnUiThread(new Runnable() {
          @Override
          public void run() {
            updatePosition(seekZoomLength / 2, seekZoomLength);
          }
        });

        seekZoomTimerStart();
      }
    }, 1000);
  }

  private void seekZoomTimerStop() {
    seekZoomTimer.cancel();
    seekZoomTimer.purge();
    seekZoomTimer = new Timer();
  }

  private void seekZoomReset() {
    seekZoomTimerStop();
    seekZoomBegin = 0;
    seekZoomLength = 0;
  }

  private synchronized void setupEmptyView() {
    if (isEmpty) return;

    setContentView(R.layout.songqueue_empty);
    isEmpty = true;
  }

  private synchronized void setupContentView() {
    if (!isEmpty) return;

    setContentView(R.layout.songqueue);

    playButton = (Button) findViewById(R.id.playButton);
    skipButton = (Button) findViewById(R.id.skipButton);
    songTimeTextView = (TextView) findViewById(R.id.songTime);
    songSeekBar = (SeekBar) findViewById(R.id.songSeekBar);
    enqueuedSongsListView = (ListView) findViewById(R.id.enqueuedSongs);

    enqueuedSongsListView.setAdapter(new MainSongAdapter(this, player.getEnqueuedSongs()));

    playButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        switch (SongQueue.this.player.getState()) {
          case IS_PLAYING:
            SongQueue.this.player.stop();
            break;
          case IS_STOPPED:
          case IS_PAUSED:
            SongQueue.this.player.play();
            break;
        }
      }
    });

    skipButton.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        SongQueue.this.player.playNext();
      }
    });

    /*
     * We send a seek request when the user has lifted his finger.
     */
    songSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
      private int lastProgress = 0;

      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
        isDraggingSeekBar = false;
        player.seek(SongQueue.this.seekZoomBegin + lastProgress);
        seekZoomReset();
      }

      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
        isDraggingSeekBar = true;
        seekZoomTimerStart();
      }

      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (fromUser) {
          lastProgress = progress;
          updatePosition(progress, -1);
          seekZoomTimerStart();
        }
      }
    });

    registerForContextMenu(enqueuedSongsListView);
    enqueuedSongsListView.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        view.showContextMenu();
      }
    });

    isEmpty = false;
  }

  /**
   * Update visually song position.
   * 
   * Updates the seekBar and songText.
   * 
   * @param position
   * @param duration
   */
  private void updatePosition(int position, int duration) {
    if (duration == -1) duration = songSeekBar.getMax();

    if (duration > 0) {
      String seekZoomText = "";
      int start = position + seekZoomBegin;
      int songDuration = player.getDuration();

      if (seekZoomLength != 0) {
        seekZoomText = String.format("[%d:%02d - %d:%02d]", (seekZoomBegin / 1000) / 60,
            (seekZoomBegin / 1000) % 60,
            ((seekZoomBegin + seekZoomLength) / 1000) / 60,
            ((seekZoomBegin + seekZoomLength) / 1000) % 60);
      }

      songTimeTextView.setText(String.format("%d:%02d / %d:%02d (%d%%)%s",
          (start / 1000) / 60, (start / 1000) % 60, (songDuration / 1000) / 60,
          (songDuration / 1000) % 60, Math.round(100 * start / songDuration),
          seekZoomText));
    }
    else {
      songTimeTextView.setText("");
    }
    songSeekBar.setMax(duration);
    songSeekBar.setProgress(position);
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setupEmptyView();

    startService(new Intent(SongQueue.this, Player.class));
    bindService(new Intent(SongQueue.this, Player.class), playerConnection, 0);
  }

  @Override
  public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
    selectedSong = (Song) enqueuedSongsListView.getItemAtPosition(info.position);

    menu.add(R.string.context_menu_play_now).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            player.reset();
            player.setPlaying(selectedSong);
            player.play();
            return false;
          }
        });

    menu.add(R.string.context_menu_play_next).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            player.removeSong(selectedSong);
            player.enqueueSong(selectedSong, Arrays
                .asList(player.getEnqueuedSongs())
                .indexOf(player.getPlaying()) + 1);
            return false;
          }
        });

    menu.add(R.string.context_menu_move_up).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            player.moveSong(selectedSong, -1);
            return false;
          }
        });

    menu.add(R.string.context_menu_move_down).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            player.moveSong(selectedSong, 1);
            return false;
          }
        });

    menu.add(R.string.context_menu_remove).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            player.removeSong(selectedSong);
            return false;
          }
        });

    menu.add(R.string.context_menu_clone).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            player.enqueueSong(selectedSong.spawn(), -1);
            return false;
          }
        });

    super.onCreateContextMenu(menu, v, menuInfo);
  }

  @Override
  public boolean onCreateOptionsMenu(android.view.Menu menu) {
    menu.add(R.string.option_menu_remove_all).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            new AlertDialog.Builder(SongQueue.this)
                .setTitle(R.string.option_menu_remove_all)
                .setMessage(R.string.dialog_are_you_sure)
                .setNegativeButton(R.string.dialog_no, null)
                .setPositiveButton(R.string.dialog_yes,
                    new Dialog.OnClickListener() {
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                        player.stop();
                        for (Song song : player.getEnqueuedSongs()) {
                          player.removeSong(song);
                        }
                      }
                    })
                .show();
            return false;
          }
        });

    menu.add(R.string.option_menu_shuffle).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            new AlertDialog.Builder(SongQueue.this)
                .setTitle(R.string.option_menu_shuffle)
                .setMessage(R.string.dialog_are_you_sure)
                .setNegativeButton(R.string.dialog_no, null)
                .setPositiveButton(R.string.dialog_yes,
                    new Dialog.OnClickListener() {
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                        player.stop();

                        List<Song> songsNew = Arrays.asList(player
                            .getEnqueuedSongs());
                        for (Song song : songsNew) {
                          player.removeSong(song);
                        }
                        Collections.shuffle(songsNew);
                        for (Song song : songsNew) {
                          player.enqueueSong(song, -1);
                        }
                        if (songsNew.size() > 0) {
                          player.setPlaying(songsNew.get(0));
                          player.play();
                        }
                      }
                    })
                .show();
            return false;
          }
        });

    menu.add(R.string.option_menu_enqueue_new).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            startActivity(new Intent(SongQueue.this, SongList.class)
                .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
            return false;
          }
        });

    menu.add(R.string.option_menu_exit).setOnMenuItemClickListener(
        new OnMenuItemClickListener() {
          @Override
          public boolean onMenuItemClick(MenuItem item) {
            new AlertDialog.Builder(SongQueue.this)
                .setTitle(R.string.option_menu_exit)
                .setMessage(R.string.dialog_are_you_sure)
                .setNegativeButton(R.string.dialog_no, null)
                .setPositiveButton(R.string.dialog_yes,
                    new Dialog.OnClickListener() {
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                        if (SongList.INSTANCE != null)
                          SongList.INSTANCE.finish();
                        unbindService(playerConnection);
                        stopService(new Intent(SongQueue.this, Player.class));
                        finish();
                      }
                    })
                .show();
            return false;
          }
        });

    return super.onCreateOptionsMenu(menu);
  }

  @Override
  public void onBackPressed() {
    moveTaskToBack(true);
  }

  private class MainSongAdapter extends SongAdapter {
    public MainSongAdapter(Context context, Song[] songs) {
      super(context, songs);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      View v = super.getView(position, convertView, parent);
      if (v != null) {
        Song[] songs = player.getEnqueuedSongs();

        if (position < songs.length && Song.equals(player.getPlaying(), songs[position])) {
          v.setBackgroundDrawable(getResources().getDrawable(
              R.drawable.listitem_selector_first));
        }
        else {
          v.setBackgroundDrawable(getResources()
              .getDrawable(R.drawable.listitem_selector));
        }
      }
      return v;
    }
  };
}




Java Source Code List

com.michalkazior.simplemusicplayer.Player.java
com.michalkazior.simplemusicplayer.SongAdapter.java
com.michalkazior.simplemusicplayer.SongList.java
com.michalkazior.simplemusicplayer.SongQueue.java
com.michalkazior.simplemusicplayer.Song.java