Sử dụng MediaPlayer để chạy Video trong Android

Về mặt kỹ thuật, một số thiết bị điện thoại di động đã có khả năng hỗ trợ video từ trước 2004. Tuy nhiên trên thực tế, video trên điện thoại di động chỉ thực sự phổ biến từ sự ra đời của iPhone vào năm 2007. Kể từ đó, mỗi chiếc điện thoại thông minh đều được hỗ trợ play video. Và với Android cũng như vậy.

Những định dạng (format) được hỗ trợ

Android hỗ trợ những định dạng thông thường mà các thiết bị di động khác được hỗ trợ. Đó là định dạng 3GP (.3gp) và MPEG-4 (.mp4). 3GP là một chuẩn video có nguồn gốc từ MPEG-4 đặc biệt để sử dụng cho các thiết bị di động.

Kể từ Android 3.0 trở lên được hỗ trợ thêm định dạng WebM.

Sử dụng MediaPlayer

Các trạng thái của MediaPlayer

Đối tượng MediaPlayer hoạt động theo máy trạng thái. Có nghĩa là các hoạt động phải được thực hiện theo một thứ tự cụ thể và các phương thức khác nhau chỉ được gọi khi đối tượng đang ở trạng thái chính xác để xử lý chúng.

Lớp MediaPlayer định nghĩa một vài listener cho phép ứng dụng sử dụng nó để lắng nghe những thông báo về các thay đổi trạng thái khác nhau để có những hành động phù hợp.

Sơ đồ trạng thái của MediaPlayer mediaplayer_state_diagram.gif

Ví dụ cụ thể

Sau đây là ví dụ cụ thể sử dụng MediaPlayer để play video trong ứng dụng.

Import MediaPlayer và các interface để sử dụng:

    import android.media.MediaPlayer;
    import android.media.MediaPlayer.OnCompletionListener;
    import android.media.MediaPlayer.OnErrorListener;
    import android.media.MediaPlayer.OnInfoListener;
    import android.media.MediaPlayer.OnPreparedListener;
    import android.media.MediaPlayer.OnSeekCompleteListener;
    import android.media.MediaPlayer.OnVideoSizeChangedListener;

SurfaceHolder và SurfaceView sẽ được sử dụng để draw video:

    import android.view.SurfaceHolder;
    import android.view.SurfaceView;

Activity sẽ implement tất cả các listener lắng nghe thay đổi trạng thái của MediaPlayer và interface SurfaceHolder.Callback cho phép nhận được thông báo về thay đổi của một SurfaceView.

    public class CustomVideoPlayer extends Activity implements OnCompletionListener, OnErrorListener, OnInfoListener, OnPreparedListener, OnSeekCompleteListener, OnVideoSizeChangedListener, SurfaceHolder.Callback {

        Display currentDisplay;
        SurfaceView surfaceView;
        SurfaceHolder surfaceHolder;
        ...
    }

Khai báo đối tượng MediaPlayer

    MediaPlayer mediaPlayer;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

Tham chiếu đến đối tượng SurfaceView được định nghĩa trong layout XML và tham chiếu đến SurfaceHolder cho phép theo dõi những thay đổi xảy ra đối với video.

    surfaceView = (SurfaceView)this.findViewById(R.id.SurfaceView);
    surfaceHolder = surfaceView.getHolder();

Vì activity đã implement SurfaceHolder.Callback, chúng ta sẽ add lắng nghe callback cho SurfaceHolder:

    surfaceHolder.addCallback(this);

Bây giờ, bắt đầu tạo đối tượng MediaPlayer, không truyền tham số để nhận được đối tượng ở trạng thái "idle".

    mediaPlayer = new MediaPlayer();

Chỉ định lắng nghe các sự kiện khác nhau:

    mediaPlayer.setOnCompletionListener(this);
    mediaPlayer.setOnErrorListener(this);
    mediaPlayer.setOnInfoListener(this);
    mediaPlayer.setOnPreparedListener(this);
    mediaPlayer.setOnSeekCompleteListener(this);
    mediaPlayer.setOnVideoSizeChangedListener(this);

Trước khi kết thúc phương thức onCreate(), truyền vào đối tượng video cho MediaPlayer thông qua đường dẫn filePath. Phương thức setDataSource() trên MediaPlayer có thể nhảy vào các ngoại lệ.

    try {
        mediaPlayer.setDataSource(filePath);
    } catch (IllegalArgumentException e) {
        Log.v(LOGTAG,e.getMessage());
        finish();
    } catch (IllegalStateException e) {
        Log.v(LOGTAG,e.getMessage());
        finish();
    } catch (IOException e) {
        Log.v(LOGTAG,e.getMessage());
        finish();
    }

Vì activity đã implement SurfaceHolder.CallbacksurfaceHolder đã đăng ký lắng nghe các callback, nên 3 phương thức sau sẽ được kích hoạt.

surfaceCreated sẽ được gọi khi Surface trong SurfaceView được tạo.

    public void surfaceCreated(SurfaceHolder holder) {
        Log.v(LOGTAG,"surfaceCreated Called");

Khi Surface được tạo ra, chúng ta có thể xác định rằng các MediaPlayer sử dụng Surface để play bằng cách gọi phương thức setDisplay truyền vào đối tượng SurfaceHolder.

    mediaPlayer.setDisplay(holder);

Cuối cùng, sau khi đã xác định Surface, chúng ta có thể gọi prepare. Phương thức prepare sẽ block thread chính chứ không thực hiện trong thread background. Để thực hiện ở chế độ background, không để ứng dụng bị treo, sử dụng phương thức prepareAsyn. Dù bằng cách nào, vì OnPreparedListener đã được implement trong activity, phương thức onPrepared sẽ được gọi khi thực hiện xong prepare.

Phương thức prepare có thể nhảy ra các ngoại lệ:

    try {
        mediaPlayer.prepare();
    } catch (IllegalStateException e) {
        Log.v(LOGTAG,e.getMessage());
        finish();
    } catch (IOException e) {
        Log.v(LOGTAG,e.getMessage());
        finish();
    }

surfaceChanged sẽ được gọi khi width, height hoặc tham số khác của SurfaceView thay đổi.

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
        Log.v(LOGTAG,"surfaceChanged Called");
    }

surfaceDestroyed sẽ được gọi khi Surface của SurfaceView bị huỷ.

    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.v(LOGTAG,"surfaceDestroyed Called");
    }

MediaPlayer.OnCompletionListener đã được implement và đăng ký lắng nghe, phương thức onCompletion sẽ được gọi khi MediaPlayer play xong video.

    public void onCompletion(MediaPlayer mp) {
        Log.v(LOGTAG,"onCompletion Called");
        finish();
    }

Activity có implement MediaPlayer.OnErrorListener và được đăng ký lắng nghe cho đối tượng MediaPlayer, do đó phương thức onError sẽ được gọi khi có vấn đề xảy ra. Không may là không có nhiều thông tin lỗi, chỉ có 2 hằng số như sau:

    public boolean onError(MediaPlayer mp, int whatError, int extra) {
        Log.v(LOGTAG,"onError Called");
        if (whatError == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
            Log.v(LOGTAG,"Media Error, Server Died " + extra);
        } else if (whatError == MediaPlayer.MEDIA_ERROR_UNKNOWN){
            Log.v(LOGTAG,"Media Error, Error Unknown " + extra);
        }

        return false;
    }

Return false từ phương thức này chỉ ra rằng các lỗi đã không được xử lý. Nếu một OnCompletionListener được đăng ký, phương thức onCompletion sẽ được gọi. Đối tượng MediaPlayer sẽ được chuyển sang trạng thái "error". Nó có thể trở về trạng thái "idle" nếu gọi tới phương thức reset.

Phương thức onInfo được đặc tả trong OnInfoListener được gọi khi có được thông tin cụ thể về video hoặc một cảnh báo được đưa ra.

    public boolean onInfo(MediaPlayer mp, int whatInfo, int extra) {
        if (whatInfo == MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING){
            Log.v(LOGTAG,"Media Info, Media Info Bad Interleaving " + extra);
        } else if (whatInfo == MediaPlayer.MEDIA_INFO_NOT_SEEKABLE) {
            Log.v(LOGTAG,"Media Info, Media Info Not Seekable " + extra);
        } else if (whatInfo == MediaPlayer.MEDIA_INFO_UNKNOWN) {
            Log.v(LOGTAG,"Media Info, Media Info Unknown " + extra);
        } else if (whatInfo == MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING) {
            Log.v(LOGTAG,"MediaInfo, Media Info Video Track Lagging " + extra);
        }

        return false;
    }

Sau khi MediaPlayer prepare thành công, onPrepared sẽ được gọi. Khi đó video có thể được start.

    public void onPrepared(MediaPlayer mp) {
        Log.v(LOGTAG,"onPrepared Called");
        mp.start();
    }

Kết quả màn hình ứng dụng sử dụng MediaPlayer Screenshot_20160427-160242.png