Monday, May 5, 2014

Implement time bar for MediaPlayer, also introduce bug of MediaPlayer.seekTo()

Continuous from last post of MediaPlay MP3 Player, a SeekBar is added to show the current playing position.

Also user can seek to a specified time by moving the SeekBar, by calling seekTo() of MediaPlayer. But the seekTo() method work in Nexus One (Android 2.3.6), not work on Nexus 7 1st generation (Android 4.4.2). It seem to be a bug!


Modify fragment_main.xml to add a SeekBar.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.androidmp3player.MainActivity$PlaceholderFragment" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />
    
    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start" />
    <Button
        android:id="@+id/pause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pause" />
    <Button
        android:id="@+id/stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop" />
    <Button
        android:id="@+id/seekto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Seek To Beginning" />
    <SeekBar 
        android:id="@+id/time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="0"/>
    <TextView
        android:id="@+id/state"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/duration"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/position"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>


MainActivity.java
package com.example.androidmp3player;

import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.Fragment;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnSeekCompleteListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  if (savedInstanceState == null) {
   getSupportFragmentManager().beginTransaction()
     .add(R.id.container, new PlaceholderFragment()).commit();
  }
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {

  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }

 /**
  * A placeholder fragment containing a simple view.
  */
 public static class PlaceholderFragment extends Fragment {

  Button btnStart, btnPause, btnStop, btnSeek;
  TextView textState, textDuration, textPosition;
  SeekBar timeBar;

  MediaPlayer mediaPlayer;

  private int stateMediaPlayer;
  private final int STATE_Idle = 0;
  private final int STATE_Initialized = 1;
  private final int STATE_Preparing = 2;
  private final int STATE_Prepared = 3;
  private final int STATE_Started = 4;
  private final int STATE_Paused = 5;
  private final int STATE_Stopped = 6;
  private final int STATE_PlaybackCompleted = 7;
  private final int STATE_End = 8;
  private final int STATE_Error = 9;
  
  PlayerTimeTask playerTimeTask;
  
  public PlaceholderFragment() {
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
   // TODO Auto-generated method stub
   super.onCreate(savedInstanceState);
   initMediaPlayer();
   playerTimeTask = new PlayerTimeTask();
   playerTimeTask.execute();
  }

  @Override
  public void onDestroy() {
   
   playerTimeTask.setRunning(false);

   Toast.makeText(getActivity(), 
    "release mediaPlayer", 
    Toast.LENGTH_LONG).show();
   mediaPlayer.release();
   mediaPlayer = null;
   setPlayerState(STATE_End);
   
   super.onDestroy();
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   View rootView = inflater.inflate(R.layout.fragment_main, container,
     false);
   btnStart = (Button) rootView.findViewById(R.id.start);
   btnPause = (Button) rootView.findViewById(R.id.pause);
   btnStop = (Button) rootView.findViewById(R.id.stop);
   btnSeek = (Button) rootView.findViewById(R.id.seekto);
   
   btnStart.setOnClickListener(new OnClickListener() {
    
    @Override
    public void onClick(View v) {
     
     if(stateMediaPlayer==STATE_Prepared 
      || stateMediaPlayer==STATE_Started
      || stateMediaPlayer==STATE_Paused
      || stateMediaPlayer==STATE_PlaybackCompleted){
      mediaPlayer.start();
      setPlayerState(STATE_Started);
      
      displayDurationPosition();
     }else{
      Toast.makeText(getActivity(), 
       "Play at Invalid state!", 
       Toast.LENGTH_LONG).show();
     }
    }
   });
   
   btnPause.setOnClickListener(new OnClickListener() {
    
    @Override
    public void onClick(View v) {
     
     if(stateMediaPlayer==STATE_Started
      || stateMediaPlayer==STATE_Paused
      || stateMediaPlayer==STATE_Prepared //simulate a error case
      || stateMediaPlayer==STATE_PlaybackCompleted){
      mediaPlayer.pause();
      setPlayerState(STATE_Paused);
      
      displayDurationPosition();
     }else{
      Toast.makeText(getActivity(), 
       "Pause at Invalid state!", 
       Toast.LENGTH_LONG).show();
     }
    }
   });
   
   btnStop.setOnClickListener(new OnClickListener() {
    
    @Override
    public void onClick(View v) {
     
     if(stateMediaPlayer==STATE_Prepared
      || stateMediaPlayer==STATE_Started
      || stateMediaPlayer==STATE_Stopped
      || stateMediaPlayer==STATE_Paused
      || stateMediaPlayer==STATE_PlaybackCompleted){
      
      //Stop
      mediaPlayer.stop();
      setPlayerState(STATE_Stopped);
      
      //then parepare in background thread
      mediaPlayer.prepareAsync();
      setPlayerState(STATE_Preparing);
      
      displayDurationPosition();
     }else{
      Toast.makeText(getActivity(), 
       "Stop at Invalid state!", 
       Toast.LENGTH_LONG).show();
     }
     
    }
   });
   
   btnSeek.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     if(stateMediaPlayer==STATE_Prepared
      || stateMediaPlayer==STATE_Started
      || stateMediaPlayer==STATE_Paused
      || stateMediaPlayer==STATE_PlaybackCompleted){
      mediaPlayer.seekTo(0);
      
      displayDurationPosition();
     }else{
      Toast.makeText(getActivity(), 
       "SeekTo at Invalid state!", 
       Toast.LENGTH_LONG).show();
     }
    }});
   
   timeBar = (SeekBar) rootView.findViewById(R.id.time);
   timeBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
    
    int targetPos = 0;
    boolean rqsSeek = false;
    
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

     if(rqsSeek){

      if(mediaPlayer!=null){
       if(stateMediaPlayer==STATE_Prepared
        || stateMediaPlayer==STATE_Started
        || stateMediaPlayer==STATE_Paused
        || stateMediaPlayer==STATE_PlaybackCompleted){
        
        mediaPlayer.seekTo(targetPos);
       }
      }
      
      rqsSeek = false;
     }
     
    }
    
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {}
    
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress,
      boolean fromUser) {
     
     if(fromUser){
      rqsSeek = true;

      if(mediaPlayer!=null){
       float seekToPercentage = (float)progress/100.0f;
       targetPos = (int)(mediaPlayer.getDuration() * seekToPercentage);
      }
     }
     
    }
   });

   textState = (TextView) rootView.findViewById(R.id.state);
   textState.setText(getPlayerState());
   
   textDuration = (TextView) rootView.findViewById(R.id.duration);
   textPosition = (TextView) rootView.findViewById(R.id.position);
   displayDurationPosition();
   
   textDuration.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     displayDurationPosition();
    }});
   
   textPosition.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View v) {
     displayDurationPosition();
    }});
   
   return rootView;
  }
  
  private void displayDurationPosition(){
   textDuration.setText(
     "Duration: " + mediaPlayer.getDuration() + " ms");
   textPosition.setText(
     "Current Position: " + mediaPlayer.getCurrentPosition() + " ms");
  }

  private void initMediaPlayer() {
   Toast.makeText(getActivity(), 
     "initMediaPlayer()", 
     Toast.LENGTH_LONG).show();
   mediaPlayer = MediaPlayer.create(getActivity(), R.raw.vespers);
   setPlayerState(STATE_Prepared);
   
   mediaPlayer.setOnPreparedListener(new OnPreparedListener(){

    @Override
    public void onPrepared(MediaPlayer mp) {
     setPlayerState(STATE_Prepared);
     displayDurationPosition();
    }});
   
   mediaPlayer.setOnCompletionListener(new OnCompletionListener(){

    @Override
    public void onCompletion(MediaPlayer mp) {
     setPlayerState(STATE_PlaybackCompleted);
     displayDurationPosition();
    }});
   
   //Handle Error
   mediaPlayer.setOnErrorListener(new OnErrorListener(){

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
     
     setPlayerState(STATE_Error);
     
     String errorWhat;
     switch(what){
     case MediaPlayer.MEDIA_ERROR_UNKNOWN:
      errorWhat = "MEDIA_ERROR_UNKNOWN";
      break;
     case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
      errorWhat = "MEDIA_ERROR_SERVER_DIED";
      break;
     default:
      errorWhat = "!";
     }
     
     String errorExtra;
     switch(extra){
     case MediaPlayer.MEDIA_ERROR_IO:
      errorExtra = "MEDIA_ERROR_IO";
      break;
     case MediaPlayer.MEDIA_ERROR_MALFORMED:
      errorExtra = "MEDIA_ERROR_MALFORMED";
      break;
     case MediaPlayer.MEDIA_ERROR_UNSUPPORTED:
      errorExtra = "MEDIA_ERROR_UNSUPPORTED";
      break;
     case MediaPlayer.MEDIA_ERROR_TIMED_OUT:
      errorExtra = "MEDIA_ERROR_TIMED_OUT";
      break;
     default:
      errorExtra = "!";
     }

     Toast.makeText(getActivity(), 
      "Error" + "\n"
      + errorWhat + "\n"
      + errorExtra,
      Toast.LENGTH_LONG).show();
     
     //release 
     mp.release();
     initMediaPlayer();
     
     return true;
    }});
   
   mediaPlayer.setOnSeekCompleteListener(new OnSeekCompleteListener() {
    
    @Override
    public void onSeekComplete(MediaPlayer mp) {
     Toast.makeText(getActivity(), 
      "OnSeekComplete: " + mp.getCurrentPosition(), 
      Toast.LENGTH_SHORT).show();

    }
   });
  }
  
  private void setPlayerState(int st){
   stateMediaPlayer = st;
   
   String stringState = getPlayerState();
   if(textState!=null){
    textState.setText(stringState);
   }else{
    Toast.makeText(getActivity(), 
      stringState, Toast.LENGTH_LONG).show();
   }
   
  }
  
  private String getPlayerState(){
   String strSt;
   switch(stateMediaPlayer){
   case STATE_Idle:
    strSt = "Idle";
    break;
   case STATE_Initialized:
    strSt = "Initialized";
    break;
   case STATE_Preparing:
    strSt = "Preparing";
    break;
   case STATE_Prepared:
    strSt = "Prepared";
    break;
   case STATE_Started:
    strSt = "Started";
    break;
   case STATE_Paused:
    strSt = "Paused";
    break;
   case STATE_Stopped:
    strSt = "Stopped";
    break;
   case STATE_PlaybackCompleted:
    strSt = "PlaybackCompleted";
    break;
   case STATE_End:
    strSt = "End";
    break;
   case STATE_Error:
    strSt = "Error";
    break;
   default:
    strSt = "unknown...";
   }
   return strSt;
  }
  
  public class PlayerTimeTask extends AsyncTask<Void, Void, Void> {

   boolean running;
   
   public PlayerTimeTask() {
    running = true;
   }
   
   public void setRunning(boolean r){
    running = r;
   }

   @Override
   protected Void doInBackground(Void... params) {
    while(running){
     SystemClock.sleep(250);
     publishProgress();
    }
    return null;
   }

   @Override
   protected void onProgressUpdate(Void... values) {
    if(timeBar!=null){

     if(mediaPlayer!=null){
       
      if(stateMediaPlayer == STATE_Prepared
       || stateMediaPlayer == STATE_Started
       || stateMediaPlayer == STATE_Paused
       || stateMediaPlayer == STATE_Stopped
       || stateMediaPlayer == STATE_PlaybackCompleted){
       int cur = mediaPlayer.getCurrentPosition();
       int dur = mediaPlayer.getDuration();
       int timePercentage = 100 * cur/dur;
       timeBar.setProgress(timePercentage);
      }
      
      textPosition.setText(
        "Current Position: " 
        + mediaPlayer.getCurrentPosition() + " ms");
     }

    }
   }


  }
 }
 
}


download filesDownload the files, not include mp3.

Downlaod and try the APK.

No comments: