Saturday, November 8, 2014

Android: MediaPlayer

In this tutorial we will learn how to play MP3 file saved in the application. We will create an app that plays a soothing sound of the forest (or waves, or rain) that plays in the loop. We will also learn how to control the playback.


Step 1: Create a new Module "appNatureSounds"

  • min API 8
Step 2: Design UI of your player




<RelativeLayout 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"
                tools:context=".MainActivity">
    <TextView
            android:id="@+id/uiSoundTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Song Title"/>
    <LinearLayout
            android:id="@+id/controlLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_alignParentBottom="true">
        <ImageButton
                android:id="@+id/uiFastForward"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="20"
                android:layout_marginBottom="14dp"
                android:onClick="forward"
                android:src="@android:drawable/ic_media_ff"/>
        <ImageButton
                android:layout_weight="40"
                android:id="@+id/uiPlayButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="24dp"
                android:onClick="play"
                android:src="@android:drawable/ic_media_play"/>
        <ImageButton
                android:layout_weight="20"
                android:id="@+id/uiPausePlay"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="14dp"
                android:onClick="pause"
                android:src="@android:drawable/ic_media_pause"/>
        <ImageButton
                android:layout_weight="20"
                android:id="@+id/uiRewind"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_marginLeft="22dp"
                android:onClick="rewind"
                android:src="@android:drawable/ic_media_rew"/>
    </LinearLayout>
    <LinearLayout
            android:id="@+id/progressLayout"
            android:layout_above="@id/controlLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
        <TextView
                android:id="@+id/uiCurrentTime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="15"
                android:text="@string/zero_time"
                android:textAppearance="?android:attr/textAppearanceSmall"/>
        <SeekBar
                android:id="@+id/uiProgressSeekBar"
                android:layout_weight="70"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        <TextView
                android:id="@+id/uiDuration"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="right"
                android:layout_gravity="right"
                android:layout_weight="15"
                android:text="@string/zero_time"
                android:textAppearance="?android:attr/textAppearanceSmall"/>
    </LinearLayout>
    <ImageView
            android:id="@+id/uiPlayerBackground"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@id/progressLayout"
            android:layout_below="@+id/uiSoundTitle"
            android:src="@drawable/bg_rainforest"/>
</RelativeLayout>



Step:  Add any language specific text




<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Nature Sounds</string>
    <string name="action_settings">Settings</string>
    <string name="zero_time">0:0</string>
</resources>


Step: Record, or find a SHORT royalty-free MP3 you want to play





Step: create a class that will hold the TIME of the song.



package com.chicagoandroid.cit299.naturesounds.appnaturesounds;
import java.text.SimpleDateFormat;
/**
 * Wrapper for time mm:ss
 * Created by uki on 11/8/14.
 */
public class PlayTime {
   long time;
   public PlayTime(long value) {
      time = value;
   }
   public void update(int value) {
      time = (long) value;
   }
   public void add(int value) {
      time = time + value;
   }
   public boolean subtract(int value) {
      if (time - value > 0) {
         time = time - value;
         return true;
      }
      return false;
   }
   public PlayTime(int value) {
      time = (long) value;
   }
   public int toInt() {
      return (int) time;
   }
   public String toString() {
      SimpleDateFormat ft = new SimpleDateFormat("mm:ss");
      return ft.format(time);
   }
}





Step: MainActivity.OnCreate()



package com.chicagoandroid.cit299.naturesounds.appnaturesounds;
import android.media.MediaPlayer;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.widget.*;
/** Nature Sounds app */
public class MainActivity extends ActionBarActivity {
   private static int FORWARD_TIME = 5000;
   private static int REWIND_TIME = 5000;
   private static int DELAY_TIME = 100; // update 10 times per second
   private TextView soundTitle, currentTimeLabel, durationTimeLabel;
   private ImageView backgroundImage;
   private MediaPlayer mediaPlayer;
   private PlayTime currentTime = new PlayTime(0);
   private PlayTime durationTime = new PlayTime(0);
   private Handler threadHandler = new Handler();;
   private SeekBar progressSeekBar;
   private ImageButton playButton, pauseButton;
   private boolean userChangingProgress = false;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      soundTitle = (TextView) findViewById(R.id.uiSoundTitle);
      currentTimeLabel = (TextView) findViewById(R.id.uiCurrentTime);
      durationTimeLabel = (TextView) findViewById(R.id.uiDuration);
      progressSeekBar = (SeekBar) findViewById(R.id.uiProgressSeekBar);
      playButton = (ImageButton) findViewById(R.id.uiPlayButton);
      pauseButton = (ImageButton) findViewById(R.id.uiPausePlay);
      backgroundImage = (ImageView) findViewById(R.id.uiPlayerBackground);
      // set UI values to initial state before play
      durationTimeLabel.setText(durationTime.toString());
      currentTimeLabel.setText(currentTime.toString());
      progressSeekBar.setClickable(false);
      pauseButton.setEnabled(false);
      // this could be loaded from the database
      soundTitle.setText("Rain forest");
      mediaPlayer = MediaPlayer.create(this, R.raw.sound_001);
      backgroundImage.setImageResource(R.drawable.bg_rainforest);
   }


Step: play method


   public void play(View view) {
      //play sound over and over
      mediaPlayer.setLooping(true);
      mediaPlayer.start();
      toast("Playing sound");
      durationTime.update(mediaPlayer.getDuration());
      currentTime.update(mediaPlayer.getCurrentPosition());
      durationTimeLabel.setText(durationTime.toString());
      currentTimeLabel.setText(currentTime.toString());
      progressSeekBar.setProgress(currentTime.toInt());
      progressSeekBar.setMax(durationTime.toInt());
      pauseButton.setEnabled(true);
      playButton.setEnabled(false);
      threadHandler.postDelayed(SongUpdateThread, DELAY_TIME);
      progressSeekBar.setOnSeekBarChangeListener(new SeekBarListener());
   }




Step: create  SeekBarListener





   class SeekBarListener implements OnSeekBarChangeListener {
      /**
       * Notification that the progress level has changed. Clients can use the fromUser parameter
       * to distinguish user-initiated changes from those that occurred programmatically.
       *
       * @param seekBar The SeekBar whose progress has changed
       * @param progress The current progress level. This will be in the range 0..max where max
       * was set by {@link android.widget.ProgressBar#setMax(int)}. (The default value for max is 100.)
       * @param fromUser True if the progress change was initiated by the user.
       */
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
         if (fromUser) {
            currentTime.update(progress);
            mediaPlayer.seekTo(currentTime.toInt());
         }
      }
      /**
       * Notification that the user has started a touch gesture. Clients may want to use this
       * to disable advancing the seekbar.
       *
       * @param seekBar The SeekBar in which the touch gesture began
       */
      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
         userChangingProgress = true;
      }
      /**
       * Notification that the user has finished a touch gesture. Clients may want to use this
       * to re-enable advancing the seekbar.
       *
       * @param seekBar The SeekBar in which the touch gesture began
       */
      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
         userChangingProgress = false;
      }
   }


Step: additional control methods




   public void pause(View view) {
      toast("Pausing sound");
      mediaPlayer.pause();
      pauseButton.setEnabled(false);
      playButton.setEnabled(true);
   }
   public void forward(View view) {
      int temp = currentTime.toInt();
      if ((temp + FORWARD_TIME) <= durationTime.toInt()) {
         currentTime.add(FORWARD_TIME);
         mediaPlayer.seekTo(currentTime.toInt());
      }
      else {
         toast("Cannot jump forward " + FORWARD_TIME / 1000 + " seconds");
      }
   }
   public void rewind(View view) {
      if ((currentTime.toInt() - REWIND_TIME) > 0) {
         currentTime.subtract(REWIND_TIME);
         mediaPlayer.seekTo(currentTime.toInt());
      }
      else {
         toast("Cannot jump backward " + REWIND_TIME / 1000 + " seconds");
      }
   }



Step: utility methods


 void toast(String text) {
      Toast toast = Toast.makeText(getApplicationContext(), text, Toast.LENGTH_LONG);
      toast.setGravity(Gravity.CENTER | Gravity.CENTER, 0, 0);
      toast.show();
   }
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.main, menu);
      return true;
   }


Step: Song Update Thread


   private Runnable SongUpdateThread = new Runnable() {
      public void run() {
         if (!userChangingProgress) {
            currentTime.update(mediaPlayer.getCurrentPosition());
            progressSeekBar.setProgress(currentTime.toInt());
         }
         currentTimeLabel.setText(currentTime.toString());
         // call itself
         threadHandler.postDelayed(this, DELAY_TIME);
      }
   };



res/dimens.xml


   
    16dp
    16dp