목차
Progress Bar, Seek Bar
progress bar 를 움직였을 때 그 곳 부터 play 할려 한다면 palyer 의 seekTo() 를 호출해야 한다. (PlaybackService.get(this).seekToProgress() 안에서 호출한다.)그 부분에 대한 code 는 아래와 같다.
onProgressChanged()
--> send a message, MSG_SEEK_TO_PROGRESS
--> handleMessage
--> PlaybackService.get(this).seekToProgress()
--> updateElapsedTime()
// FullPlaybackActivity.java
// implements SeekBar.OnSeekBarChangeListener
public class FullPlaybackActivity extends PlaybackActivity
implements SeekBar.OnSeekBarChangeListener
, View.OnLongClickListener
private SeekBar mSeekBar;
@Override
public void onCreate(Bundle icicle)
{
mSeekBar = (SeekBar)findViewById(R.id.seek_bar);
mSeekBar.setMax(1000); // set the max of the progress range
mSeekBar.setOnSeekBarChangeListener(this);
...
}
...
/**
* Update seek bar progress and schedule another update in one second
*/
private void updateElapsedTime()
{
long position = PlaybackService.hasInstance() ? PlaybackService.get(this).getPosition() : 0;
if (!mSeekBarTracking) {
long duration = mDuration;
mSeekBar.setProgress(duration == 0 ? 0 : (int)(1000 * position / duration));
}
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, position / 1000));
if (!mPaused && mControlsVisible && (mState & PlaybackService.FLAG_PLAYING) != 0) {
// Try to update right after the duration increases by one second
long next = 1050 - position % 1000;
mUiHandler.removeMessages(MSG_UPDATE_PROGRESS);
mUiHandler.sendEmptyMessageDelayed(MSG_UPDATE_PROGRESS, next);
}
}
...
//--------------------------------------------------------- SeekBar.OnSeekBarChangeListener
//
...
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
if (fromUser) {
// set elapsed time
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, progress * mDuration / 1000000));
// send a message for seeking
mUiHandler.removeMessages(MSG_SEEK_TO_PROGRESS);
mUiHandler.sendMessageDelayed(mUiHandler.obtainMessage(MSG_SEEK_TO_PROGRESS, progress, 0), 150);
}
}
//------------------------------------------------------------------------ handleMessage
/**
* Calls {@link #seekToProgress()}.
*/
private static final int MSG_SEEK_TO_PROGRESS = 18;
@Override
public boolean handleMessage(Message message)
{
switch (message.what) {
...
case MSG_SEEK_TO_PROGRESS:
PlaybackService.get(this).seekToProgress(message.arg1); // practical action
updateElapsedTime(); // update the progress bar and time
break;
default:
return super.handleMessage(message);
}
return true;
}
}
// PlaybackService.java public final class PlaybackService extends Service implements Handler.Callback , MediaPlayer.OnCompletionListener , MediaPlayer.OnErrorListener , SharedPreferences.OnSharedPreferenceChangeListener , SongTimeline.Callback , SensorEventListener , AudioManager.OnAudioFocusChangeListener { ... /** * Seek to a position in the current song. * * @param progress Proportion of song completed (where 1000 is the end of the song) */ public void seekToProgress(int progress) { if (!mMediaPlayerInitialized) return; long position = (long)mMediaPlayer.getDuration() * progress / 1000; mMediaPlayer.seekTo((int)position); } ... }
Update Progress bar
노래등 음악파일이 재생될 때 progress bar 가 계속 해서 udpate 되도록 하는 부분이다.주기적으로 updateElapsedTime() 를 호출하는 것이 방법이다. 이를 위해서 MSG_UPDATE_PROGRESS 를 이용한다.
그리고 Service 와의 synch 를 위해 service 에서 값을 가져오고 있다. (PlaybackService.get(this).getPosition())
// FullPlaybackActivity.java
public class FullPlaybackActivity extends PlaybackActivity
implements SeekBar.OnSeekBarChangeListener
, View.OnLongClickListener
private SeekBar mSeekBar;
@Override
public void onCreate(Bundle icicle)
{
...
setDuration(0);
}
@Override
public void onResume()
{
super.onResume();
mPaused = false;
updateElapsedTime();
}
@Override
public void onPause()
{
super.onPause();
mPaused = true;
}
...
/**
* Update the current song duration fields.
*
* @param duration The new duration, in milliseconds.
*/
private void setDuration(long duration)
{
mDuration = duration;
mDurationView.setText(DateUtils.formatElapsedTime(mTimeBuilder, duration / 1000));
}
...
@Override
protected void onStateChange(int state, int toggled)
{
super.onStateChange(state, toggled);
...
if ((state & PlaybackService.FLAG_PLAYING) != 0)
updateElapsedTime();
...
}
@Override
protected void onSongChange(Song song)
{
super.onSongChange(song);
setDuration(song == null ? 0 : song.duration);
...
mCurrentSong = song;
updateElapsedTime();
if (mExtraInfoVisible) {
mHandler.sendEmptyMessage(MSG_LOAD_EXTRA_INFO);
}
}
...
/**
* Update seek bar progress and schedule another update in one second
*/
private void updateElapsedTime()
{
long position = PlaybackService.hasInstance() ? PlaybackService.get(this).getPosition() : 0;
if (!mSeekBarTracking) {
long duration = mDuration;
mSeekBar.setProgress(duration == 0 ? 0 : (int)(1000 * position / duration));
}
mElapsedView.setText(DateUtils.formatElapsedTime(mTimeBuilder, position / 1000));
if (!mPaused && mControlsVisible && (mState & PlaybackService.FLAG_PLAYING) != 0) {
// Try to update right after the duration increases by one second
long next = 1050 - position % 1000;
mUiHandler.removeMessages(MSG_UPDATE_PROGRESS);
mUiHandler.sendEmptyMessageDelayed(MSG_UPDATE_PROGRESS, next);
}
}
@Override
public boolean handleMessage(Message message)
{
switch (message.what) {
...
case MSG_UPDATE_PROGRESS:
updateElapsedTime();
break;
...
}
}
}
Rewind button
player button
button 을 누를 때 UI thread 에서 하는 일은 cover view 를 update 하는 정도이다. 실제 동작은 PlaybackService#shiftCurrentSong 을 통해 이루어 진다.
// FullPlaybackActivity.java public void onCreate(Bundle icicle){ ... View previousButton = findViewById(R.id.previous); previousButton.setOnClickListener(this); ... } // PlaybackActivity.java @Override public void onClick(View view) { switch (view.getId()) { case R.id.next: shiftCurrentSong(SongTimeline.SHIFT_NEXT_SONG); break; ... case R.id.previous: shiftCurrentSong(SongTimeline.SHIFT_PREVIOUS_SONG); break; ... } } @Override public void shiftCurrentSong(int delta) { setSong(PlaybackService.get(this).shiftCurrentSong(delta)); } protected void setSong(final Song song) { mLastSongEvent = SystemClock.uptimeMillis(); runOnUiThread(new Runnable() { @Override public void run() { onSongChange(song); } }); } /** * Called when the current song changes. * * @param song The new song */ protected void onSongChange(Song song) { // about Cover view if (mCoverView != null) mCoverView.querySongs(PlaybackService.get(this)); }
// PlaybackService.java /** * Move to next or previous song or album in the queue. * * @param delta One of SongTimeline.SHIFT_*. * @return The new current song. */ public Song shiftCurrentSong(int delta) { Song song = setCurrentSong(delta); userActionTriggered(); return song; } /** * Move to the next or previous song or album in the timeline. * * @param delta One of SongTimeline.SHIFT_*. 0 can also be passed to * initialize the current song with media player, notification, * broadcasts, etc. * @return The new current song */ private Song setCurrentSong(int delta) { if (mMediaPlayer == null) return null; if (mMediaPlayer.isPlaying()) mMediaPlayer.stop(); Song song; if (delta == 0) song = mTimeline.getSong(0); else song = mTimeline.shiftCurrentSong(delta); mCurrentSong = song; if (song == null || song.id == -1 || song.path == null) { if (MediaUtils.isSongAvailable(getContentResolver())) { int flag = finishAction(mState) == SongTimeline.FINISH_RANDOM ? FLAG_ERROR : FLAG_EMPTY_QUEUE; synchronized (mStateLock) { updateState((mState | flag) & ~FLAG_NO_MEDIA); } return null; } else { // we don't have any songs : / synchronized (mStateLock) { updateState((mState | FLAG_NO_MEDIA) & ~FLAG_EMPTY_QUEUE); } return null; } } else if ((mState & (FLAG_NO_MEDIA|FLAG_EMPTY_QUEUE)) != 0) { synchronized (mStateLock) { updateState(mState & ~(FLAG_EMPTY_QUEUE|FLAG_NO_MEDIA)); } } mHandler.removeMessages(PROCESS_SONG); mMediaPlayerInitialized = false; mHandler.sendMessage(mHandler.obtainMessage(PROCESS_SONG, song)); mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_CHANGE, -1, 0, song)); return song; } /** * Resets the idle timeout countdown. Should be called by a user action * has been triggered (new song chosen or playback toggled). * * If an idle fade out is actually in progress, aborts it and resets the * volume. */ public void userActionTriggered() { mHandler.removeMessages(FADE_OUT); mHandler.removeMessages(IDLE_TIMEOUT); ... long idleStart = mIdleStart; if (idleStart != -1 && SystemClock.elapsedRealtime() - idleStart < IDLE_GRACE_PERIOD) { mIdleStart = -1; setFlag(FLAG_PLAYING); } }
notification button
// PlaybackService.java public Notification createNotification(Song song, int state){ ... Intent previous = new Intent(PlaybackService.ACTION_PREVIOUS_SONG); previous.setComponent(service); expanded.setOnClickPendingIntent(R.id.previous, PendingIntent.getService(this, 0, previous, 0)); ... } public int onStartCommand(Intent intent, int flags, int startId){ ... else if (ACTION_PREVIOUS_SONG.equals(action)) { setCurrentSong(-1); userActionTriggered(); } else if (ACTION_REWIND_SONG.equals(action)) { /* only rewind song IF we played more than 2.5 sec (and song is longer than 5 sec) */ if(getPosition() > REWIND_AFTER_PLAYED_MS && getDuration() > REWIND_AFTER_PLAYED_MS*2) { setCurrentSong(0); } else { setCurrentSong(-1); } play(); }else if (ACTION_NEXT_SONG.equals(action)) { setCurrentSong(1); userActionTriggered(); } ... }
댓글 없음:
댓글 쓰기