2016年8月9日 星期二

Android: Audio Player

since: 2016/08/09
update: 2016/08/10

reference:
1. Android MediaPlayer and VideoView Tutorial

2. [Android 開發] 裝置方向改變時,不重新創建 activity 的方法

A. Create a Project
    1. 設定好專案相關資料後 > Next

    2. Minimum SDK 選擇 API 18: Android 4.3 (Jelly Bean)
        > Next

3. 選擇 Empty Activity > Next

4. Customize the Activity: 使用預設即可 > Finish

-----------------------------------------------------------------------------------------------

B. Create raw folder to store audio files
    1. res > New > Directory

    2. 輸入 raw > OK

    3. Copy & Paste audio files to raw folder:

-----------------------------------------------------------------------------------------------

C. Design the Interface:

     1. 點選 activity_main.xml 檔案, 新增元件onClick 事件如下:

    2. 元件 的 ID, 顯示文字與 onClick 事件 

    3. activity_main.xml 完成如下:(需再自行調整)
<?xml version="1.0" encoding="utf-8"?>
<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="cat.myhome.audioplayer.MainActivity">

    <SeekBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/seekBar"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="&lt;Max Time>"
        android:id="@+id/textView_maxTime"
        android:layout_below="@+id/seekBar"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="38dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:gravity="center" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="&lt;Current Position>"
        android:id="@+id/textView_currentPosion"
        android:layout_marginTop="65dp"
        android:gravity="center"
        android:layout_below="@+id/textView_maxTime"
        android:layout_centerHorizontal="true" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="&lt;&lt;"
        android:id="@+id/button_rewind"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="93dp"
        android:onClick="doRewind"
        android:layout_below="@+id/textView_currentPosion"
        android:layout_alignParentStart="true" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"
        android:id="@+id/button_start"
        android:onClick="doStart"
        android:layout_alignTop="@+id/button_rewind"
        android:layout_toEndOf="@+id/button_rewind"
        android:layout_marginLeft="30dp" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Pause"
        android:id="@+id/button_pause"
        android:onClick="doPause"
        android:layout_alignTop="@+id/button_start"
        android:layout_toStartOf="@+id/button_fastForward"
        android:layout_marginRight="30dp" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=">>"
        android:id="@+id/button_fastForward"
        android:onClick="doFastForward"
        android:layout_alignTop="@+id/button_pause"
        android:layout_alignEnd="@+id/textView_maxTime"
        android:layout_marginRight="20dp" />

</RelativeLayout>


-----------------------------------------------------------------------------------------------

D. When the device orientation changes, not re-created activity method
     1. 開啟 AndroidManifest.xml 檔案, 修改如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cat.myhome.audioplayer">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
                  android:configChanges="orientation|keyboardHidden|screenSize">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

p.s. screenSize : added in API level 13


-----------------------------------------------------------------------------------------------

E. 開啟 MainActivity.java 檔案, 修改如下:
package cat.myhome.audioplayer;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;


//@AudioPlayer ------
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.media.MediaPlayer;
import android.os.Handler;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

    //@AudioPlayer ------
    private TextView textMaxTime;
    private TextView textCurrentPosition;
    private Button buttonPause;
    private Button buttonStart;
    private SeekBar seekBar;
    private Handler threadHandler = new Handler();

    private MediaPlayer mediaPlayer;

    //@AudioPlayer: add variables ------
    private int duration = 0;
    private int currentPosition = 0;
    private String maxTimeString = "";
    private String currentTimeString = "";

    //@AudioPlayer: data defined ------
    // 5 second per step for Forward or Rewind
    private int perTimeStep = 5000;
    // 0.2 second for error allowed time: (duration - currentPosition)
    private int errorAllowedTime = 200;


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

        //@AudioPlayer ------
        this.textMaxTime =(TextView) this.findViewById(R.id.textView_maxTime);
        this.textCurrentPosition = (TextView)this.findViewById(R.id.textView_currentPosion);

        this.buttonStart = (Button) this.findViewById(R.id.button_start);
        this.buttonPause = (Button) this.findViewById(R.id.button_pause);

        this.buttonPause.setEnabled(false);

        this.seekBar = (SeekBar) this.findViewById(R.id.seekBar);
        this.seekBar.setClickable(false);

        // ID of 'mysong' in 'raw' folder
        int songId = this.getRawResIdByName("marcus");

        // Create MediaPlayer
        this.mediaPlayer = MediaPlayer.create(this, songId);

        //@AudioPlayer: set textMaxTime ------
        this.duration = this.mediaPlayer.getDuration();//@update #######
        this.maxTimeString = this.millisecondsToString(this.duration);
        this.textMaxTime.setText(this.maxTimeString);

        //@AudioPlayer: set CurrentPosition ------
        this.currentPosition = this.mediaPlayer.getCurrentPosition();
        this.currentTimeString = this.millisecondsToString(this.currentPosition);
        this.textCurrentPosition.setText(this.currentTimeString);

        //@AudioPlayer: set seekBar Max ------
        this.seekBar.setMax(this.duration);

        //@AudioPlayer: When the audio file ready for playback ------
        this.mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.seekTo(currentPosition);
                mediaPlayer.start();
                // Create a thread to update position of SeekBar
                UpdateSeekBarThread updateSeekBarThread = new UpdateSeekBarThread();
                threadHandler.postDelayed(updateSeekBarThread,50);

                buttonPause.setEnabled(true);
                buttonStart.setEnabled(false);
            }
        });

    }


    //@AudioPlayer: Find ID of resource in 'raw' folder ------
    public int getRawResIdByName(String resName)  {
        String pkgName = this.getPackageName();
        // Return 0 if not found.
        int resID = this.getResources().getIdentifier(resName, "raw", pkgName);
        return resID;
    }


    //@AudioPlayer: Convert millisecond to string ------
    private String millisecondsToString(int milliseconds)  {
        long minutes = TimeUnit.MILLISECONDS.toMinutes((long) milliseconds);
        long seconds =  TimeUnit.MILLISECONDS.toSeconds((long) milliseconds) ;

        //@AudioPlayer: update convert handle ------
        seconds = seconds - (minutes * 60);

        String minutesStr = "" + minutes;
        String secondsStr = "" + seconds;

        if(seconds < 10) {
            secondsStr = "0" + seconds;
        }

        if(minutes < 10) {
            minutesStr = "0" + minutes;
        }

        return minutesStr+":"+ secondsStr;
        //return minutes+":"+ seconds;
    }


    //@AudioPlayer ------
    public void doStart(View view)  {

        //@AudioPlayer: update ------
        this.currentPosition = this.mediaPlayer.getCurrentPosition(); //@update ########

        if(this.currentPosition == 0)  {
            //DO Nothing
        } else if(this.currentPosition == this.duration)  {
            // Resets the MediaPlayer to its uninitialized state.
            this.mediaPlayer.reset();
        }

        this.mediaPlayer.start();

        // Create a thread to update position of SeekBar.
        UpdateSeekBarThread updateSeekBarThread = new UpdateSeekBarThread();
        threadHandler.postDelayed(updateSeekBarThread,50);

        this.buttonPause.setEnabled(true);
        this.buttonStart.setEnabled(false);
    }


    //@AudioPlayer: Thread to Update position for SeekBar ------
    class UpdateSeekBarThread implements Runnable {

        public void run()  {

            currentPosition = mediaPlayer.getCurrentPosition();
            //duration = mediaPlayer.getDuration();

            String currentPositionStr = millisecondsToString(currentPosition);
            textCurrentPosition.setText(currentPositionStr);

            seekBar.setProgress(currentPosition);

            //if(currentPosition == duration) {
            // errorAllowedTime = 0.2 second
            if((currentPosition + errorAllowedTime) >= duration) {

                buttonPause.setEnabled(false);
                buttonStart.setEnabled(true);
            }

            // Delay thread 50 milisecond.
            threadHandler.postDelayed(this, 50);
        }
    }


    //@AudioPlayer: When user click to "Pause" ------
    public void doPause(View view)  {

        this.mediaPlayer.pause();
        this.buttonPause.setEnabled(false);
        this.buttonStart.setEnabled(true);
    }


    //@AudioPlayer: When user click to "Rewind" ------
    public void doRewind(View view)  {

        this.currentPosition = this.mediaPlayer.getCurrentPosition();

        //int SUBTRACT_TIME = 5000; // 5 seconds.
        int SUBTRACT_TIME = this.perTimeStep; // 5 seconds.
        if(this.currentPosition - SUBTRACT_TIME > 0 )  {

            this.mediaPlayer.seekTo(this.currentPosition - SUBTRACT_TIME);
        }
        else {
            this.mediaPlayer.seekTo(0);
        }
    }


    //@AudioPlayer: When user click to "Fast-Forward" ------
    public void doFastForward(View view)  {

        this.currentPosition = this.mediaPlayer.getCurrentPosition();

        //int ADD_TIME = 5000; // 5 seconds.
        int ADD_TIME = this.perTimeStep; // 5 seconds.

        if(this.currentPosition + ADD_TIME < this.duration)  {
            this.mediaPlayer.seekTo(this.currentPosition + ADD_TIME);
        }
        else {
            //Do Nothing
        }
    }

   

    //@AudioPlayer: onPause ------    @Override   
    protected void onPause() {
        super.onPause();

        this.mediaPlayer.pause();
    }


    //@AudioPlayer: onStop ------
    @Override
    protected void onStop() {
        super.onStop();

        this.mediaPlayer.stop();
    }


    //@AudioPlayer: (not used now) ------
    // When you change direction of phone, this method will be called.
    // It store the state of video (Current position)
    /*
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        // Store current position.
        savedInstanceState.putInt("currentPosition", this.mediaPlayer.getCurrentPosition());
    }
    */

    //@AudioPlayer: (not used now) ------
    // After rotating the phone. This method is called.
    /*
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        // Get saved position.
        int currentPosition = savedInstanceState.getInt("currentPosition");
        Log.d("currentPosition: ", "" + currentPosition);
    }
    */

}


-----------------------------------------------------------------------------------------------

F. 結果:

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。