Showing posts with label MP3 Player. Show all posts
Showing posts with label MP3 Player. Show all posts

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.

Sunday, May 4, 2014

Re-set On..Listener after mediaPlayer.release and re-create MediaPlayer

It's a BIG bug in last exercise Implement OnErrorListener for MP3 Player using MediaPlayer: after we release the old mediaPlayer, and create again, the original OnPreparedListener(), OnCompletionListener() and OnErrorListener() are not associate with the new mediaPlayer; and will not be called. So we should move all code of setOnPreparedListener(), setOnCompletionListener() and setOnErrorListener() to initMediaPlayer(); to update the Listeners after new mediaPlayer created.

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.os.Bundle;
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.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;

  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;
  
  public PlaceholderFragment() {
  }

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

  @Override
  public void onDestroy() {

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

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

}


download filesDownload the files, without mp3.

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

Implement OnErrorListener for MP3 Player using MediaPlayer

The former post show how to Implement Android MP3 Player using MediaPlayer, without handle error case. To detect error for MediaPlayer, we can implement our OnErrorListener, and add it to MediaPlayer by calling setOnErrorListener().

Please note that this exercise show how to implement and add OnErrorListener, not how to solve the error.


To simpulate error in our exercise, modify the OnClickListener of btnPause, to accept if (stateMediaPlayer==STATE_Prepared), its a invalide case. When user click on the Pause button when the MediaPlayer is in STATE_Prepared, error will occur. Without OnErrorListener, OnCompletionListener will be called, many error message will be dispalyed in LogCat, and the MP3 cannot be play anymore.

Modify MainActivity.java in the former exercise to implement OnErrorListener. When error occur, release the old MediaPlay and instantiate a new one by calling initMediaPlayer().

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.os.Bundle;
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.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;

  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;
  
  public PlaceholderFragment() {
  }

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

  @Override
  public void onDestroy() {

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

}


Keep using the layout files in last exercise.

download filesDownload the files, without mp3 file.

Remark: It's a BIG bug here, read next post: Re-set On..Listener after mediaPlayer.release and re-create MediaPlayer.

Thursday, May 1, 2014

Implement Android MP3 Player using MediaPlayer

MediaPlayer class can be used to control playback of audio/video files and streams. It is a example to implement MP3 Player using MediaPlayer. Please note that you have to keep follow the State Diagram, otherwise IllegalStateException will be thrown.


Create a new Android Project in Eclipse, using the auto-generated code extend from ActionBarActivity.

The mp3 file is stored in /res/raw/vespers.mp3. Filename match with the following code in initMediaPlayer().

mediaPlayer = MediaPlayer.create(getActivity(), R.raw.vespers);

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.OnPreparedListener;
import android.os.Bundle;
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.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;

  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;
  
  public PlaceholderFragment() {
  }

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

  @Override
  public void onDestroy() {

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

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

}

fragment_main.xml
<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" />
    <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>

Auto generated activity_main.xml.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.androidmp3player.MainActivity"
    tools:ignore="MergeRootFrame" />


download filesDownload the files (not include the mp3 file).

Next:
Implement OnErrorListener.
Re-set On..Listener after mediaPlayer.release and re-create MediaPlayer
Implement time bar for MediaPlayer, also introduce bug of MediaPlayer.seekTo()