[컴][안드로이드] vanilla Music player 소스 분석 - progress bar, rewind, fast forward






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();
 } 
 ...
 
}
 








댓글 없음:

댓글 쓰기