diff --git a/res/drawable/next.xml b/res/drawable/next.xml
new file mode 100644
index 00000000..b1a1746b
--- /dev/null
+++ b/res/drawable/next.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/next_focused.png b/res/drawable/next_focused.png
new file mode 100644
index 00000000..9d654d60
Binary files /dev/null and b/res/drawable/next_focused.png differ
diff --git a/res/drawable/next_normal.png b/res/drawable/next_normal.png
new file mode 100644
index 00000000..2552f4ed
Binary files /dev/null and b/res/drawable/next_normal.png differ
diff --git a/res/drawable/pause.xml b/res/drawable/pause.xml
new file mode 100644
index 00000000..8b279211
--- /dev/null
+++ b/res/drawable/pause.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/pause_focused.png b/res/drawable/pause_focused.png
new file mode 100644
index 00000000..c0730b0a
Binary files /dev/null and b/res/drawable/pause_focused.png differ
diff --git a/res/drawable/pause_normal.png b/res/drawable/pause_normal.png
new file mode 100644
index 00000000..61df5c86
Binary files /dev/null and b/res/drawable/pause_normal.png differ
diff --git a/res/drawable/play.xml b/res/drawable/play.xml
new file mode 100644
index 00000000..1a04f654
--- /dev/null
+++ b/res/drawable/play.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/play_focused.png b/res/drawable/play_focused.png
new file mode 100644
index 00000000..233a5655
Binary files /dev/null and b/res/drawable/play_focused.png differ
diff --git a/res/drawable/play_normal.png b/res/drawable/play_normal.png
new file mode 100644
index 00000000..2092bd57
Binary files /dev/null and b/res/drawable/play_normal.png differ
diff --git a/res/drawable/previous.xml b/res/drawable/previous.xml
new file mode 100644
index 00000000..12d33989
--- /dev/null
+++ b/res/drawable/previous.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/previous_focused.png b/res/drawable/previous_focused.png
new file mode 100644
index 00000000..ba6a3bef
Binary files /dev/null and b/res/drawable/previous_focused.png differ
diff --git a/res/drawable/previous_normal.png b/res/drawable/previous_normal.png
new file mode 100644
index 00000000..05eba718
Binary files /dev/null and b/res/drawable/previous_normal.png differ
diff --git a/res/layout/nowplaying.xml b/res/layout/nowplaying.xml
new file mode 100644
index 00000000..61485ad7
--- /dev/null
+++ b/res/layout/nowplaying.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/org/kreed/tumult/CoverView.java b/src/org/kreed/tumult/CoverView.java
index e1642a6a..5276db3f 100755
--- a/src/org/kreed/tumult/CoverView.java
+++ b/src/org/kreed/tumult/CoverView.java
@@ -8,6 +8,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -32,17 +33,17 @@ public class CoverView extends View {
public interface CoverViewWatcher {
public void next();
public void previous();
- public void togglePlayback();
+ public void clicked();
}
- public CoverView(Context context)
+ public CoverView(Context context, AttributeSet attributes)
{
- super(context);
+ super(context, attributes);
mScroller = new Scroller(context);
}
- public void setCoverSwapListener(CoverViewWatcher listener)
+ public void setWatcher(CoverViewWatcher listener)
{
mListener = listener;
}
@@ -254,7 +255,7 @@ public class CoverView extends View {
break;
case MotionEvent.ACTION_UP:
if (Math.abs(mStartX - x) + Math.abs(mStartY - ev.getY()) < 10) {
- mListener.togglePlayback();
+ mListener.clicked();
} else {
VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
diff --git a/src/org/kreed/tumult/IMusicPlayerWatcher.aidl b/src/org/kreed/tumult/IMusicPlayerWatcher.aidl
index f81d0232..0a060931 100644
--- a/src/org/kreed/tumult/IMusicPlayerWatcher.aidl
+++ b/src/org/kreed/tumult/IMusicPlayerWatcher.aidl
@@ -6,4 +6,5 @@ oneway interface IMusicPlayerWatcher {
void previousSong(in Song playingSong, in Song nextForwardSong);
void nextSong(in Song playingSong, in Song nextBackwardSong);
void stateChanged(in int oldState, in int newState);
+ void mediaLengthChanged(in long startTime, in int duration);
}
\ No newline at end of file
diff --git a/src/org/kreed/tumult/IPlaybackService.aidl b/src/org/kreed/tumult/IPlaybackService.aidl
index fc36e11f..89fcee56 100644
--- a/src/org/kreed/tumult/IPlaybackService.aidl
+++ b/src/org/kreed/tumult/IPlaybackService.aidl
@@ -7,8 +7,12 @@ interface IPlaybackService {
void registerWatcher(IMusicPlayerWatcher watcher);
Song[] getCurrentSongs();
+ int getState();
+ long getStartTime();
+ int getDuration();
void previousSong();
void togglePlayback();
void nextSong();
+ void seekToProgress(int progress);
}
\ No newline at end of file
diff --git a/src/org/kreed/tumult/MusicPlayer.java b/src/org/kreed/tumult/MusicPlayer.java
index 86dd4d48..129796c5 100644
--- a/src/org/kreed/tumult/MusicPlayer.java
+++ b/src/org/kreed/tumult/MusicPlayer.java
@@ -29,12 +29,32 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
public static final int STATE_NORMAL = 0;
public static final int STATE_NO_MEDIA = 1;
+ public static final int STATE_PLAYING = 2;
public IPlaybackService.Stub mBinder = new IPlaybackService.Stub() {
public Song[] getCurrentSongs()
{
return new Song[] { getSong(-1), getSong(0), getSong(1) };
}
+
+ public int getState()
+ {
+ return mState;
+ }
+
+ public long getStartTime()
+ {
+ if (mMediaPlayer == null)
+ return 0;
+ return MusicPlayer.this.getStartTime();
+ }
+
+ public int getDuration()
+ {
+ if (mMediaPlayer == null)
+ return 0;
+ return mMediaPlayer.getDuration();
+ }
public void nextSong()
{
@@ -62,6 +82,16 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
if (watcher != null)
mWatchers.register(watcher);
}
+
+ public void seekToProgress(int progress)
+ {
+ if (mMediaPlayer == null || !mMediaPlayer.isPlaying())
+ return;
+
+ long position = (long)mMediaPlayer.getDuration() * progress / 1000;
+ mMediaPlayer.seekTo((int)position);
+ mediaLengthChanged();
+ }
};
public void queueSong(int songId)
@@ -201,6 +231,9 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
public void setState(int state)
{
+ if (mState == state)
+ return;
+
int oldState = mState;
mState = state;
@@ -244,12 +277,15 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
notification.contentIntent = PendingIntent.getActivity(mService, 0, intent, 0);
mService.startForegroundCompat(NOTIFICATION_ID, notification);
+
+ setState(STATE_PLAYING);
}
private void pause()
{
mMediaPlayer.pause();
mService.stopForegroundCompat(NOTIFICATION_ID);
+ setState(STATE_NORMAL);
}
private void setPlaying(boolean play)
@@ -291,6 +327,8 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
Log.e("Tumult", "IOException", e);
}
+ mediaLengthChanged();
+
getSong(+2);
while (mCurrentSong > 15) {
@@ -298,6 +336,28 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
--mCurrentSong;
}
}
+
+ private long getStartTime()
+ {
+ int position = mMediaPlayer.getCurrentPosition();
+ return System.currentTimeMillis() - position;
+ }
+
+ private void mediaLengthChanged()
+ {
+ long start = getStartTime();
+ int duration = mMediaPlayer.getDuration();
+
+ int i = mWatchers.beginBroadcast();
+ while (--i != -1) {
+ try {
+ mWatchers.getBroadcastItem(i).mediaLengthChanged(start, duration);
+ } catch (RemoteException e) {
+ // Null elements will be removed automatically
+ }
+ }
+ mWatchers.finishBroadcast();
+ }
public void onCompletion(MediaPlayer player)
{
@@ -306,7 +366,7 @@ public class MusicPlayer implements Runnable, MediaPlayer.OnCompletionListener,
private Song randomSong()
{
- return new Song(mSongs[mRandom.nextInt(mSongs.length)]);
+ return new Song(mSongs[mRandom.nextInt(mSongs.length)]);
}
private synchronized Song getSong(int delta)
diff --git a/src/org/kreed/tumult/NowPlayingActivity.java b/src/org/kreed/tumult/NowPlayingActivity.java
index c60d80c2..145abdb0 100644
--- a/src/org/kreed/tumult/NowPlayingActivity.java
+++ b/src/org/kreed/tumult/NowPlayingActivity.java
@@ -8,22 +8,41 @@ import android.content.Context;
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.RemoteException;
import android.util.Log;
import android.view.Gravity;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.SeekBar;
import android.widget.TextView;
-public class NowPlayingActivity extends Activity implements CoverViewWatcher, ServiceConnection {
+public class NowPlayingActivity extends Activity implements CoverViewWatcher, ServiceConnection, View.OnClickListener, SeekBar.OnSeekBarChangeListener {
private IPlaybackService mService;
+
+ private ViewGroup mLayout;
private CoverView mCoverView;
private LinearLayout mMessageBox;
+ private View mControls;
+
+ private ImageButton mPreviousButton;
+ private ImageButton mPlayPauseButton;
+ private ImageButton mNextButton;
+ private SeekBar mSeekBar;
+ private TextView mSeekText;
+
+ private int mState;
+ private long mStartTime;
+ private int mDuration;
+ private boolean mSeekBarTracking;
- private static final int MENU_PREVIOUS = 0;
- private static final int MENU_NEXT = 1;
private static final int MENU_PREFS = 2;
private static final int MENU_QUEUE = 3;
@@ -32,18 +51,40 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
{
super.onCreate(icicle);
- mCoverView = new CoverView(this);
- mCoverView.setCoverSwapListener(this);;
- setContentView(mCoverView);
-// Bundle extras = getIntent().getExtras();
+ setContentView(R.layout.nowplaying);
+
+ mCoverView = (CoverView)findViewById(R.id.cover_view);
+ mCoverView.setWatcher(this);
+
+ mLayout = (ViewGroup)mCoverView.getParent();
+
+ mControls = findViewById(R.id.controls);
+
+ mPreviousButton = (ImageButton)findViewById(R.id.previous);
+ mPreviousButton.setOnClickListener(this);
+ mPlayPauseButton = (ImageButton)findViewById(R.id.play_pause);
+ mPlayPauseButton.setOnClickListener(this);
+ mNextButton = (ImageButton)findViewById(R.id.next);
+ mNextButton.setOnClickListener(this);
+
+ mSeekText = (TextView)findViewById(R.id.seek_text);
+ mSeekBar = (SeekBar)findViewById(R.id.seek_bar);
+ mSeekBar.setMax(1000);
+ mSeekBar.setOnSeekBarChangeListener(this);
}
public void setState(int state)
{
+ mState = state;
+
switch (state) {
case MusicPlayer.STATE_NORMAL:
- setContentView(mCoverView);
- mMessageBox = null;
+ case MusicPlayer.STATE_PLAYING:
+ if (mMessageBox != null) {
+ mLayout.removeView(mMessageBox);
+ mMessageBox = null;
+ }
+ mPlayPauseButton.setImageResource(state == MusicPlayer.STATE_PLAYING ? R.drawable.pause : R.drawable.play);
break;
case MusicPlayer.STATE_NO_MEDIA:
mMessageBox = new LinearLayout(this);
@@ -51,11 +92,11 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
TextView text = new TextView(this);
text.setText("No songs found on your device.");
text.setGravity(Gravity.CENTER);
- LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT);
layoutParams.gravity = Gravity.CENTER;
text.setLayoutParams(layoutParams);
mMessageBox.addView(text);
- setContentView(mMessageBox);
+ mLayout.addView(mMessageBox);
break;
}
}
@@ -98,6 +139,7 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
try {
mService.registerWatcher(mWatcher);
refreshSongs();
+ setState(mService.getState());
} catch (RemoteException e) {
Log.i("Tumult", "Failed to initialize connection to playback service", e);
}
@@ -147,6 +189,12 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
}
});
}
+
+ public void mediaLengthChanged(long startTime, int duration)
+ {
+ mStartTime = startTime;
+ mDuration = duration;
+ }
};
public void next()
@@ -167,7 +215,7 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
}
}
- public void togglePlayback()
+ private void togglePlayback()
{
try {
mService.togglePlayback();
@@ -178,8 +226,6 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
- menu.add(0, MENU_PREVIOUS, 0, "Previous");
- menu.add(0, MENU_NEXT, 0, "Next");
menu.add(0, MENU_PREFS, 0, "Preferences");
menu.add(0, MENU_QUEUE, 0, "Add to Queue");
return true;
@@ -188,33 +234,139 @@ public class NowPlayingActivity extends Activity implements CoverViewWatcher, Se
@Override
public boolean onOptionsItemSelected(final MenuItem item)
{
- new Thread(new Runnable() {
- public void run()
- {
- switch (item.getItemId()) {
- case MENU_PREVIOUS:
- previous();
- break;
- case MENU_NEXT:
- next();
- break;
- case MENU_PREFS:
- startActivity(new Intent(NowPlayingActivity.this, PreferencesActivity.class));
- break;
- case MENU_QUEUE:
- onSearchRequested();
- break;
- }
- }
- }).start();
+ switch (item.getItemId()) {
+ case MENU_PREFS:
+ startActivity(new Intent(this, PreferencesActivity.class));
+ break;
+ case MENU_QUEUE:
+ onSearchRequested();
+ break;
+ }
return true;
}
-
+
@Override
public boolean onSearchRequested()
{
startActivity(new Intent(this, SongSelector.class));
return false;
}
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event)
+ {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ clicked();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void clicked()
+ {
+ mControls.setVisibility(View.VISIBLE);
+
+ if (mStartTime == 0) {
+ try {
+ mStartTime = mService.getStartTime();
+ mDuration = mService.getDuration();
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+
+ updateProgress();
+ sendHideMessage();
+ }
+
+ private String stringForTime(int ms)
+ {
+ int seconds = ms / 1000;
+
+ int hours = seconds / 3600;
+ seconds -= hours * 3600;
+ int minutes = seconds / 60;
+ seconds -= minutes * 60;
+
+ if (hours > 0)
+ return String.format("%d:%02d:%02d", hours, minutes, seconds);
+ else
+ return String.format("%02d:%02d", minutes, seconds);
+ }
+
+ private void updateProgress()
+ {
+ if (mState != MusicPlayer.STATE_PLAYING || mControls.getVisibility() != View.VISIBLE)
+ return;
+
+ long position = System.currentTimeMillis() - mStartTime;
+ if (!mSeekBarTracking)
+ mSeekBar.setProgress((int)(1000 * position / mDuration));
+ mSeekText.setText(stringForTime((int)position) + " / " + stringForTime(mDuration));
+
+ long next = 1000 - position % 1000;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(UPDATE_PROGRESS), next);
+ }
+
+ private void sendHideMessage()
+ {
+ Message message = mHandler.obtainMessage(HIDE);
+ mHandler.removeMessages(HIDE);
+ mHandler.sendMessageDelayed(message, 3000);
+ }
+
+ public void onClick(View view)
+ {
+ sendHideMessage();
+
+ if (view == mNextButton) {
+ next();
+ } else if (view == mPreviousButton) {
+ previous();
+ } else if (view == mPlayPauseButton) {
+ togglePlayback();
+ }
+ }
+
+ private static final int HIDE = 0;
+ private static final int UPDATE_PROGRESS = 1;
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case HIDE:
+ mControls.setVisibility(View.GONE);
+ break;
+ case UPDATE_PROGRESS:
+ updateProgress();
+ break;
+ }
+ }
+ };
+
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
+ {
+ if (fromUser) {
+ try {
+ mService.seekToProgress(progress);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar)
+ {
+ mHandler.removeMessages(HIDE);
+ mSeekBarTracking = true;
+ }
+
+ public void onStopTrackingTouch(SeekBar seekBar)
+ {
+ sendHideMessage();
+ mSeekBarTracking = false;
+ }
}
\ No newline at end of file