Daily Archives: 29/05/2016

Android – Music Player

Android cung cấp lớp MediaPlayer hỗ trợ play các file định dạng nhạc và phim… trong phần này chúng ta sẽ dùng lớp này đẻ xây dựng một ứng dụng nghe nhạc đơn giản.

Project

Chúng ta tạo project như bình thường. Sau đó trong thư mục res, chúng ta tạo một thư mục với tên là raw và đặt vào đó một file MP3 để sử dụng.

Capture

Layout

Bây giờ chúng ta thiết kế giao diện cho ứng dụng.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" >
    <TextView 
        android:id="@+id/songName" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:gravity="center" 
        android:text="songName"/>
 
    <LinearLayout 
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content">
    <TextView 
        android:id="@+id/remainingTime" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:textSize="20sp" 
        android:text="00:00"/> 
    <SeekBar 
        android:id="@+id/seekBar" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_weight="1"/>
    <TextView 
        android:id="@+id/songDuration" 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:textSize="20sp" android:text="00:00"/> 
    </LinearLayout>
 
    <LinearLayout 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_marginTop="30dp" 
        android:gravity="center_horizontal" 
        android:orientation="horizontal">
    <Button 
        android:id="@+id/btnPlay" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:onClick="play" android:text="Play"/>
    <Button 
        android:id="@+id/btnPause" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:onClick="pause" 
        android:text="Pause"/>
    <Button 
        android:id="@+id/btnStop" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:onClick="stop" 
        android:text="Stop"/>
    </LinearLayout>
</LinearLayout>

Ứng dụng sẽ có giao diện là 1 TextView để hiển thị tên file nhạc, 2 TextView để hiển thị thời gian của file nhạc và thời gian còn lại của file nhạc, 1 SeekBar để biểu diễn thời lượng của nhạc, 3 ButtonPlay, PauseStop.

Activity

Cuối cùng là lớp Activity.

package com.phocode;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.media.MediaPlayer;
import android.view.View;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

import java.util.concurrent.TimeUnit;

public class MainActivity extends Activity
    implements OnSeekBarChangeListener
{
    private MediaPlayer mediaPlayer;
    public TextView songName, songDuration, remainingTime; 
    private Handler updateHandler = new Handler();
    private SeekBar seekBar;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        songName = (TextView) findViewById(R.id.songName);
        songName.setText("gregg_lehrman_combust.mp3");
    
        mediaPlayer = MediaPlayer.create(this, R.raw.gregg_lehrman_combust);
 
        songDuration = (TextView)findViewById(R.id.songDuration);
        int minutes = (int)TimeUnit.MILLISECONDS.toMinutes(mediaPlayer.getDuration());
        int seconds = (int)TimeUnit.MILLISECONDS.toSeconds(mediaPlayer.getDuration()) - minutes * 60; 
        songDuration.setText(String.format("%02d:%02d",minutes, seconds));

        remainingTime = (TextView)findViewById(R.id.remainingTime);
 
        seekBar = (SeekBar)findViewById(R.id.seekBar); 
        seekBar.setMax((int) mediaPlayer.getDuration());
        seekBar.setOnSeekBarChangeListener(this);

        updateHandler = new Handler();
        updateHandler.postDelayed(update, 100);
    }
 
    public void play(View view)
    {
        mediaPlayer.start();
    }
 
    public void pause(View view)
    {
        mediaPlayer.pause();
    }
 
    public void stop(View view)
    {
        mediaPlayer.stop();
        mediaPlayer = mediaPlayer.create(this, R.raw.gregg_lehrman_combust); 
    }
 
    private Runnable update = new Runnable()
    {
        public void run()
        {
            long currentTime = mediaPlayer.getCurrentPosition();
            seekBar.setProgress((int)currentTime);
            int minutes = (int)TimeUnit.MILLISECONDS.toMinutes(currentTime);
            int seconds = (int)TimeUnit.MILLISECONDS.toSeconds(currentTime) - minutes * 60;
            remainingTime.setText(String.format("%02d:%02d",minutes, seconds));
            updateHandler.postDelayed(this, 100);
        }
    };
 
    @Override 
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
    { 
        if(fromUser)
           mediaPlayer.seekTo(progress);
    }
 
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {}
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {}
}

Lớp Activity sẽ chịu trách nhiệm xử lý việc mở file, tạm dừng, đóng file và cập nhật thời lượng hiện tại của file nhạc.

mediaPlayer = mediaPlayer.create(this, R.raw.gregg_lehrman_combust);

Để tạo một đối tượng MediaPlayer thì chúng ta dùng phương thức MediaPlayer.create(), tham số thứ nhất là đối tượng Activity, tham số thứ 2 là đối tượng file nhạc, ở đây chúng ta khai báo đường dẫn đến file nhạc nhưng không kèm theo đuôi .mp3.

songDuration = (TextView)findViewById(R.id.songDuration);
int minutes = (int)TimeUnit.MILLISECONDS.toMinutes(mediaPlayer.getDuration());
int seconds = (int)TimeUnit.MILLISECONDS.toSeconds(mediaPlayer.getDuration()) - minutes * 60; 
songDuration.setText(String.format("%02d:%02d",minutes, seconds));

Bốn dòng code trên làm công việc lấy thời gian của file nhạc và gán vào TextView dưới dạng mm:ss (phút:giây).

seekBar = (SeekBar)findViewById(R.id.seekBar); 
seekBar.setMax((int) mediaPlayer.getDuration());
seekBar.setOnSeekBarChangeListener(this);

Tiếp theo chúng ta khởi tạo đối tượng SeekBar, thiết lập thuộc tính max là thời lượng của file nhạc, sau đó gắn listener cho nó, phương thức getDuration() sẽ trả về thời lượng nhạc theo mili giây và dữ liệu trả về là kiểu long nên chúng ta ép qua kiểu int (tất nhiên bạn có thể dùng long luôn cũng được).

updateHandler = new Handler();
updateHandler.postDelayed(update, 100);

Đáng tiếc là lớp MediaPlayer không phát sinh sự kiện thay đổi thời lượng nhạc, vì thế nếu muốn biết thời lượng hiện tại là bao nhiêu thì chúng ta phải chạy tự gọi phương thức của lớp này để lấy dữ liệu, mà chúng ta lại muốn dùng dữ liệu đó để cập nhật lên SeekBar thì phải chạy trong một luồng khác, ở đây chúng ta dùng lớp Handler để làm việc này, cứ sau 1 giây thì chúng ta cập nhật lại SeekBar.

private Runnable update = new Runnable()
{
    public void run()
    {
        long currentTime = mediaPlayer.getCurrentPosition();
        seekBar.setProgress((int)currentTime);
        int minutes = (int)TimeUnit.MILLISECONDS.toMinutes(currentTime);
        int seconds = (int)TimeUnit.MILLISECONDS.toSeconds(currentTime) - minutes * 60;
        remainingTime.setText(String.format("%02d:%02d",minutes, seconds));
        updateHandler.postDelayed(this, 100);
    }
};

Lớp Handler cho phép chúng ta thực thi các công việc bên trong đối tượng Runnable hoặc Message trong các luồng khác mà không ảnh hưởng tới luồng chính. Ở đây chúng ta định nghĩa một đối tượng Runnable tên là update, đối tượng này sẽ làm công việc lấy thời lượng nhạc hiện tại và cập nhật lên SeekBar, đồng thời cập nhật lại thời gian đó trong các TextView tương ứng.

public void play(View view)
{
    mediaPlayer.start();
    updateHandler.postDelayed(update, 100); 
}

Phương thức play() sẽ được gọi khi người dùng click vào Button “Play”. Tại đây chúng ta cho MediaPlayer chạy file nhạc bằng cách gọi phương thức start().

public void pause(View view)
{
    mediaPlayer.pause();
}

public void stop(View view)
{
    mediaPlayer.stop();
    mediaPlayer = mediaPlayer.create(this, R.raw.gregg_lehrman_combust); 
}

Tương tự, muốn tạm dừng hoặc muốn dừng hẳn thì chúng ta gọi phương thức pause() và phương thức stop() tương ứng. Tuy nhiên sau khi gọi stop(), nếu muốn mở lại nhạc thì chúng ta phải reset lại tất cả trạng thái của đối tượng MediaPlayer, hoặc nhanh hơn là tạo lại một đối tượng MediaPlayer khác, như vậy mới có thể mở được.

@Override 
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{ 
    if(fromUser)
       mediaPlayer.seekTo(progress);
}

Lớp MainActivity của chúng ta có implement giao diện OnSeekBarChangeListener, mục đích là để bắt sự kiện thay đổi tiến trình trên SeekBar, bởi vì người dùng có thể dùng tay để điều khiển SeekBar chứ không chỉ riêng đối tượng Handler, nên ở đây chúng ta kiểm tra xem nếu người dùng kéo nút trên SeekBar thì chúng ta cập nhật mới vị trí của nhạc.

Dịch và chạy project, chúng ta có được ứng dụng như hình dưới.

Screenshot_2016-05-29-14-49-28

Bạn có thể kết hợp với trình duyệt file trong bài trước để nâng cấp ứng dụng lên cho hoàn thiện hơn.