วันนี้เราจะมาสร้างโปรแกรมเล่นเพลงแบบง่ายๆกันครับ โดย android มี MediaPlayer ให้เราเรียกใช้อยู่แล้ว แต่ก่อนที่เราจะใช้นั้นต้องเข้าใจถึง state ของ MediaPlayer ก่อน ดูได้จากรูปภาพครับ
โดยเมื่อเรา new object หรือเรียกใช้ method reset ขึ้นมาก็จะอยู่ใน state idle ก่อน แต่ถ้าสร้างจาก method create จะอยู่ใน state prepare ในตัวอย่างเราจะใช้การ new object ขึ้นมาใหม่ จากนั้นเรียก method setDataSource โดย method setDataSource จะต้องเรียกใช้ใน state idle เท่านั้น เมื่อสำเร็จจะเข้าสู่ state initialized ข้อสำ คัญคือ MediaPlayer จะต้องเข้าสู่ prepare state ก่อนที่จะเข้าสู่ start โดยเราสามารถเรียกใช้ method prepare (ในตัวอย่างเราเรียกใช้ method prepare แต่ถ้าเราต้องการเล่นไฟล์ที่ stream ผ่าน network ต้องเรียกใช้ method prepareAsync ) เสร็จแล้วจึงเรียกใช้ method start เพื่อเข้าสู่ start state
แล้วแต่ละ state ของ MediaPlayer สำคัญอย่างไง?เราจำเป็นต้องเข้าใจแต่ละ state ของ MediaPlayer เนื่องจากบาง method จำเป็นต้องเรียกใช้ใน state ให้ถูกต้องนั่นเอง สำหรับ type ที่ MediaPlayer support สามารถดูได้จากที่นี่
สำหรับโปรแกรมที่เราจะทำวันนี้ จะสร้าง MediaPlayer ขึ้นมาโดยเล่นไฟล์ใน raw และ percent การเล่นใน progressbar โดยผู้ใช้สามารถ pause , play stop ได้ ไปดูหน้าตาโปรแกรมครับ
Source Code สามารถ download ได้ที่นี่นะครับ ไปดูที่โค้ดกันบ้าง activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:background="@drawable/bg" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_margin="10dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:textStyle="bold" android:textColor="#ededed" android:text="Now playing : " /> <TextView android:id="@+id/track_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#ededed" android:textSize="20sp" android:text="Track" android:ellipsize="marquee" /> </LinearLayout> <ProgressBar android:id="@+id/progressbar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" /> <LinearLayout android:id="@+id/btnStart" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center" > <ImageButton android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@android:drawable/ic_media_play" /> <ImageButton android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_media_stop" /> </LinearLayout> </LinearLayout>MainActivity.java
package com.simplemediaplayer; import java.io.IOException; import android.app.Activity; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends Activity { private MediaPlayer player = null; private ProgressBar progress = null ; private final String TAG = getClass().toString(); private boolean isPause = false; private ImageButton btnStart ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnStart = (ImageButton) findViewById(R.id.btn_start); btnStart.setOnClickListener(btnStartOnClickListener); ((ImageButton) findViewById(R.id.btn_stop)).setOnClickListener(btnStopOnClickListener); progress = (ProgressBar) findViewById(R.id.progressbar); } OnClickListener btnStartOnClickListener = new OnClickListener(){ public void onClick(View v){ if(player != null ){ if(isPause){ player.start(); isPause = false; btnStart.setImageResource(android.R.drawable.ic_media_pause); }else{ player.pause(); isPause = true; btnStart.setImageResource(android.R.drawable.ic_media_play); } }else{ player = new MediaPlayer(); player.setAudioStreamType(AudioManager.STREAM_MUSIC); Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.maroon5_one_more_night ); try { player.setDataSource(getApplicationContext(), uri); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { player.prepare(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } progress.setMax(player.getDuration()); btnStart.setImageResource(android.R.drawable.ic_media_pause); ((TextView) findViewById(R.id.track_name)).setText( getResources().getString( R.raw.maroon5_one_more_night)); player.setOnCompletionListener(new OnCompletionListener(){ @Override public void onCompletion(MediaPlayer mp) { onMediaPlayerStop(); } }); player.start(); new Thread(new Runnable(){ @Override public void run(){ while(player != null ){ if( isPause ){ Log.d(TAG, "Sleep 1 sec"); }else{ progress.setProgress(player.getCurrentPosition()); Log.d(TAG, "Time : " + Integer.toString(player.getCurrentPosition())); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } } }; private void onMediaPlayerStop(){ ((TextView) findViewById(R.id.track_name)).setText(""); btnStart.setImageResource(android.R.drawable.ic_media_play); progress.setProgress(0); player.release(); player = null; } OnClickListener btnStopOnClickListener = new OnClickListener(){ public void onClick(View v){ onMediaPlayerStop(); } }; @Override public void onDestroy(){ super.onDestroy(); if(player != null){ player.release(); player = null; } } }มาดูกันทีละส่วนนะครับ
private MediaPlayer player = null; private ProgressBar progress = null ; private final String TAG = getClass().toString(); private boolean isPause = false; private ImageButton btnStart ;ก่อนอื่น ประกาศตัวแปร player ขึ้นมาเป็น เพื่อไว้สร้าง object MediaPlayer สร้าง ตัวแปร progress เพื่อเก็บ ref ของ ProgressBar ใน layout สร้างตัวแปร isPause เพื่อเก็บสถานะว่าตอนนี้ pause อยู่หรือไม่ ( MediaPlayer ไม่มี method isPause มีแต่ isPlaying ) สร้าง btnStart ขึ้นมาเพื่อเก็บ ref ปุ่ม start/pause ของเรา ไว้เพื่อเปลี่ยนสถานะของปุ่ม
จากนั้นเข้ามาใน onCreate
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnStart = (ImageButton) findViewById(R.id.btn_start); btnStart.setOnClickListener(btnStartOnClickListener); ((ImageButton) findViewById(R.id.btn_stop)).setOnClickListener(btnStopOnClickListener); progress = (ProgressBar) findViewById(R.id.progressbar); }ใน method onCreate เรากำหนดค่า btnStart, progress ให้เป็นปุ่ม start และ progress bar ตามลำดับ จากนั้นกำหนด onClickListener ให้กับปุ่ม start, stop
private void onMediaPlayerStop(){ ((TextView) findViewById(R.id.track_name)).setText(""); btnStart.setImageResource(android.R.drawable.ic_media_play); progress.setProgress(0); player.release(); player = null; } OnClickListener btnStopOnClickListener = new OnClickListener(){ public void onClick(View v){ onMediaPlayerStop(); } };จากนั้นมาดูโค้ดส่วนของ btnStopOnClickListener ที่เรากำหนดให้กับปุ่ม stop บ้าง โดยเมื่อเราคลิกปุ่ม stop ผมสร้างให้ไปเรียก method onMediaPlayerStop ที่สร้างอีก method ขึ้นมาเพื่อจะได้ไว้เรียกใช้ในตอน setOnCompletionListener ด้วย การทำงานก็ เคลียร์ค่า text ของ track ที่กำลังเล่นอยู่ กำหนด icon ของปุ่ม start กำหนดค่า Progressbar จากนั้นเรียกใช้ method release ของกำหนด player เป็น null ของบรรทัดนี้สำคัญนะครับ เมื่อเราไม่ได้ใช้ MediaPlayer ควรเขียน 2 บรรทัดนี้เพื่อคืนทรัพยากรให้กับระบบ
OnClickListener btnStartOnClickListener = new OnClickListener(){ public void onClick(View v){ if(player != null ){ if(isPause){ player.start(); isPause = false; btnStart.setImageResource(android.R.drawable.ic_media_pause); }else{ player.pause(); isPause = true; btnStart.setImageResource(android.R.drawable.ic_media_play); } }else{ player = new MediaPlayer(); player.setAudioStreamType(AudioManager.STREAM_MUSIC); Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.maroon5_one_more_night ); try { player.setDataSource(getApplicationContext(), uri); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { player.prepare(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } progress.setMax(player.getDuration()); // กำหนดค่า max ของ progressbar btnStart.setImageResource(android.R.drawable.ic_media_pause); // กำหนด icon ของปุ่มให้โชว์ icon pause ((TextView) findViewById(R.id.track_name)).setText( getResources().getString( R.raw.maroon5_one_more_night)); // set ชื่อ track player.setOnCompletionListener(new OnCompletionListener(){ // กำหนด onCompletionListener คือกำหนดว่าถ้าเล่นเสร็จแล้วจะให้ทำอะไร @Override public void onCompletion(MediaPlayer mp) { onMediaPlayerStop(); // เรียกใช้ method เดียวกับปุ่ม stop } }); player.start(); // start Media player new Thread(new Runnable(){ @Override public void run(){ while(player != null ){ if( isPause ){ Log.d(TAG, "Sleep 1 sec"); }else{ progress.setProgress(player.getCurrentPosition()); Log.d(TAG, "Time : " + Integer.toString(player.getCurrentPosition())); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } } };ทีนี้มาดูส่วนของการทำงานของปุ่ม start กันบ้างค่อนข้างยาวทีเดียว เมื่อเข้ามาใน onClick ผมเช็คก่อนว่า player != null หรือเปล่า ถ้าไม่เท่ากับ null ก็คือ MediaPlayer อยู่ในสถานะ start หรือ pause นั่นเอง ( เมื่อ stop หรือ complete ผมกำหนด player เป็น null) จากนั้นก็มาเช็คว่า pause อยู่หรือป่าว จัดการเปลี่ยนค่า isPause และรูปของปุ่ม start แต่ถ้า player == null ผมก็สร้าง object ของ MediaPlayer ขึ้นมาใหม่ ทำการ setDataSource และ prepare กำหนดค่า max ของ progress กำหนดรูปให้กับปุ่ม start แสดงชื่อแทร็ก และ start MediaPlayer
จากนั้นเราจะทำยังไงให้ Progressbar เปลี่ยนตาม MediaPlayer ? ผมไม่แน่ใจนะว่าเราสามารถทำโดยใช้วิธิอื่นได้หรือป่าว แต่ผมใช้ Thread สร้างมันขึ้นมากจากนั้นก็ loop เป็นค่า progressbar ทุกๆ วินาที โดยระหว่างนั้นเราก็ต้องเช็คด้วยว่า MediaPlayer ยังอยู่หรือไม่? และ มีการกดปุ่ม pause หรือป่าว โดยถ้ากดปุ่ม pause ผมจะใช้ sleep ไปจนกว่า user จะกด play ต่อ โค้ดการทำงานเราก็น่าจะมีแค่นี้ แต่ทางที่ดีเราควรจะเช็คก่อนที่ activity เราจะจบการทำงาน ว่า player ถูก release หรือยัง ถ้ายังก็ release เพื่อคืนทรัพยากรให้กับระบบ
@Override public void onDestroy(){ super.onDestroy(); if(player != null){ player.release(); player = null; } }เสร็จแล้วลอง run ดูครับ สำหรับโปรแกรมของเราวันนี้ถึงแม้มันจะสามารถเล่นเพลงได้แล้ว แต่ว่ามันเล่นได้แค่เพลงเดียว! จากใน raw แล้วถ้าต้องการเล่นเพลงจากในเครื่องละต้องทำยังไง? และโปรแกรมเพลงที่เราสร้างมาก็สามารถทำงานได้เฉพาะตอนที่เครื่องยังไม่ sleep เท่านั้น หน้าจอดับ โปรแกรมก็หยุด จะทำยังไงให้สามารถรันแบบ background ได้? ไว้คราวหน้าแล้วกันนะครับ