1. 程式人生 > >Android實現視訊播放的3種實現方式

Android實現視訊播放的3種實現方式

Android提供了常見的視訊的編碼、解碼機制。使用Android自帶的MediaPlayer、MediaController等類可以很方便的實現視訊播放的功能。支援的視訊格式有MP4和3GP等。這些多媒體資料可以來自於Android應用的資原始檔,也可以來自於外部儲存器上的檔案,甚至可以是來自於網路上的檔案流。

下面來說一下視訊播放的幾種實現方式:

1、MediaController+VideoView實現方式

這種方式是最簡單的實現方式。VideoView繼承了SurfaceView同時實現了MediaPlayerControl介面,MediaController則是安卓封裝的輔助控制器,帶有暫停,播放,停止,進度條等控制元件。通過VideoView+MediaController可以很輕鬆的實現視訊播放、停止、快進、快退等功能。

佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".VideoViewTestActivity">
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>

程式程式碼如下:

public class VideoViewTestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_view_test);
        VideoView videoView = (VideoView)findViewById(R.id.videoView);

	    //載入指定的視訊檔案
        String path = Environment.getExternalStorageDirectory().getPath()+"/20180730.mp4";
        videoView.setVideoPath(path);

        //建立MediaController物件
        MediaController mediaController = new MediaController(this);

	    //VideoView與MediaController建立關聯
        videoView.setMediaController(mediaController);

	    //讓VideoView獲取焦點
        videoView.requestFocus();

    }
}

使用此實現方式的步驟:

  1. 載入指定的視訊檔案
  2. 建立VideoView和MediaController之間的關聯,這樣就不需要自己去控制視訊的播放、暫停等。讓MediaController控制即可。
  3. VideoView獲取焦點。

實現效果圖如下:

介面中的快退、播放、快進、時間、進度條等是由MediaController提供的。

2、MediaPlayer+SurfaceView+自定義控制器

雖然VideoView的實現方式很簡單,但是由於是自帶的封裝好的類,所以無論是播放器的大小、位置以及控制都不受我們控制。

這種實現方式步驟如下

  1. 建立MediaPlayer物件,並讓它載入指定的視訊檔案。可以是應用的資原始檔本地文件路徑、或者URL。
  2. 在介面佈局檔案中定義SurfaceView元件,併為SurfaceView的SurfaceHolder新增Callback監聽器。
  3. 呼叫MediaPlayer物件的setDisplay(SurfaceHolder sh)將所播放的視訊影象輸出到指定的SurfaceView元件
  4. 呼叫MediaPlayer物件的prepareAsync()或prepare()方法裝載流媒體檔案
  5. 呼叫MediaPlayer物件的start()stop()和pause()方法來控制視訊的播放

在實現第二步之前需要先給surfaceHolder設定一個callbackcallback的3個回撥函式如下:

@Override
public void surfaceCreated(SurfaceHolder holder) {
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {

}

佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PlayVideoActivity">

    <RelativeLayout
        android:id="@+id/root_rl"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="#000000">

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="400dp" />
        <ImageView
            android:id="@+id/playOrPause"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_centerInParent="true"
            android:src="@android:drawable/ic_media_play"/>
        <LinearLayout
            android:id="@+id/control_ll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="#005500"
            android:orientation="vertical">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:orientation="horizontal"
                android:paddingBottom="5dp">

                <TextView
                    android:id="@+id/tv_start_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentLeft="true"
                    android:layout_marginLeft="30dp"
                    android:text="00.00"
                    android:textColor="#ffffff"/>
                <TextView
                    android:id="@+id/tv_separate_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/tv_start_time"
                    android:layout_marginLeft="1dp"
                    android:text="/"
                    android:textColor="#ffffff"/>
                <TextView
                    android:id="@+id/tv_end_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/tv_separate_time"
                    android:layout_marginLeft="1dp"
                    android:text="00.00"
                    android:textColor="#ffffff"/>
                <ImageView
                    android:id="@+id/tv_backward"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/tv_start_time"
                    android:layout_alignParentLeft="true"
                    android:layout_marginLeft="1dp"
                    android:src="@android:drawable/ic_media_rew"/>

                <SeekBar
                    android:id="@+id/tv_progess"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@+id/tv_backward"
                    android:layout_toLeftOf="@+id/tv_forward"
                    android:layout_below="@+id/tv_start_time"/>

                <ImageView
                    android:id="@+id/tv_forward"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_below="@+id/tv_start_time"
                    android:layout_alignParentRight="true"
                    android:layout_marginRight="1dp"
                    android:src="@android:drawable/ic_media_ff"/>

            </RelativeLayout>

        </LinearLayout>
    </RelativeLayout>

</android.support.constraint.ConstraintLayout>

程式程式碼如下:

public class PlayVideoActivity extends AppCompatActivity implements SurfaceHolder.Callback,
        MediaPlayer.OnPreparedListener,
        MediaPlayer.OnCompletionListener,
        MediaPlayer.OnErrorListener,
        MediaPlayer.OnInfoListener, View.OnClickListener,
        MediaPlayer.OnSeekCompleteListener,
        MediaPlayer.OnVideoSizeChangedListener,
        SeekBar.OnSeekBarChangeListener,
{

    private ImageView playOrPauseIv;
    private SurfaceView videoSuf;
    private MediaPlayer mPlayer;
    private SeekBar mSeekBar;
    private String path;
    private RelativeLayout rootViewRl;
    private LinearLayout controlLl;
    private TextView startTime, endTime;
    private ImageView forwardButton, backwardButton;
    private boolean isShow = false;

    public static final int UPDATE_TIME = 0x0001;
    public static final int HIDE_CONTROL = 0x0002;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TIME:
                    updateTime();
                    mHandler.sendEmptyMessageDelayed(UPDATE_TIME, 500);
                    break;
                case HIDE_CONTROL:
                    hideControl();
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_play_video);
        initViews();
        initData();
        initSurfaceView();
        initPlayer();
        initEvent();
    }
    private void initData() {
        path = Environment.getExternalStorageDirectory().getPath() + "/20180730.mp4";//這裡寫上你的視訊地址
    }

    private void initEvent() {
        playOrPauseIv.setOnClickListener(this);
        rootViewRl.setOnClickListener(this);
        rootViewRl.setOnTouchListener(this);
        forwardButton.setOnClickListener(this);
        backwardButton.setOnClickListener(this);
        mSeekBar.setOnSeekBarChangeListener(this);
    }
    private void initSurfaceView() {
        videoSuf = (SurfaceView) findViewById(R.id.surfaceView);
        videoSuf.setZOrderOnTop(false);
        videoSuf.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        videoSuf.getHolder().addCallback(this);
    }

    private void initPlayer() {
        mPlayer = new MediaPlayer();
        mPlayer.setOnCompletionListener(this);
        mPlayer.setOnErrorListener(this);
        mPlayer.setOnInfoListener(this);
        mPlayer.setOnPreparedListener(this);
        mPlayer.setOnSeekCompleteListener(this);
        mPlayer.setOnVideoSizeChangedListener(this);
        try {
            //使用手機本地視訊
            mPlayer.setDataSource(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initViews() {
        playOrPauseIv = (ImageView) findViewById(R.id.playOrPause);
        startTime = (TextView) findViewById(R.id.tv_start_time);
        endTime = (TextView) findViewById(R.id.tv_end_time);
        mSeekBar = (SeekBar) findViewById(R.id.tv_progess);
        rootViewRl = (RelativeLayout) findViewById(R.id.root_rl);
        controlLl = (LinearLayout) findViewById(R.id.control_ll);
        forwardButton = (ImageView) findViewById(R.id.tv_forward);
        backwardButton = (ImageView) findViewById(R.id.tv_backward);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mPlayer.setDisplay(holder);
        mPlayer.prepareAsync();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
    @Override
    public void onPrepared(MediaPlayer mp) {
        startTime.setText(FormatTimeUtil.formatLongToTimeStr(mp.getCurrentPosition()));
        endTime.setText(FormatTimeUtil.formatLongToTimeStr(mp.getDuration()));
        mSeekBar.setMax(mp.getDuration());
        mSeekBar.setProgress(mp.getCurrentPosition());
    }
    @Override
    public void onCompletion(MediaPlayer mp) {

    }
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        return false;
    }

    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        return false;
    }
    private void play() {
        if (mPlayer == null) {
            return;
        }
        Log.i("playPath", path);
        if (mPlayer.isPlaying()) {
            mPlayer.pause();
            mHandler.removeMessages(UPDATE_TIME);
            mHandler.removeMessages(HIDE_CONTROL);
            playOrPauseIv.setVisibility(View.VISIBLE);
            playOrPauseIv.setImageResource(android.R.drawable.ic_media_play);
        } else {
            mPlayer.start();
            mHandler.sendEmptyMessageDelayed(UPDATE_TIME, 500);
            mHandler.sendEmptyMessageDelayed(HIDE_CONTROL, 5000);
            playOrPauseIv.setVisibility(View.INVISIBLE);
            playOrPauseIv.setImageResource(android.R.drawable.ic_media_pause);
        }
    }
    @Override
    public void onSeekComplete(MediaPlayer mp) {
        //TODO
    }

    @Override
    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {

    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_backward:
                backWard();
                break;
            case R.id.tv_forward:
                forWard();
                break;
            case R.id.playOrPause:
                play();
                break;
            case R.id.root_rl:
                showControl();
                break;
        }
    }
    /**
     * 更新播放時間
     */
    private void updateTime() {
                
        startTime.setText(FormatTimeUtil.formatLongToTimeStr(             
             mPlayer.getCurrentPosition()));
        mSeekBar.setProgress(mPlayer.getCurrentPosition());
    }

    /**
     * 隱藏進度條
     */
    private void hideControl() {
        isShow = false;
        mHandler.removeMessages(UPDATE_TIME);
        controlLl.animate().setDuration(300).translationY(controlLl.getHeight());
    }
    /**
     * 顯示進度條
     */
    private void showControl() {
        if (isShow) {
            play();
        }
        isShow = true;
        mHandler.removeMessages(HIDE_CONTROL);
        mHandler.sendEmptyMessage(UPDATE_TIME);
        mHandler.sendEmptyMessageDelayed(HIDE_CONTROL, 5000);
        controlLl.animate().setDuration(300).translationY(0);
    }
    /**
     * 設定快進10秒方法
     */
    private void forWard(){
        if(mPlayer != null){
            int position = mPlayer.getCurrentPosition();
            mPlayer.seekTo(position + 10000);
        }
    }

    /**
     * 設定快退10秒的方法
     */
    public void backWard(){
        if(mPlayer != null){
            int position = mPlayer.getCurrentPosition();
            if(position > 10000){
                position-=10000;
            }else{
                position = 0;
            }
            mPlayer.seekTo(position);
        }
    }

    //OnSeekBarChangeListener
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
        if(mPlayer != null && b){
             mPlayer.seekTo(progress);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}

注意事項:MediaPlayer有prepareprepareAsync兩種方法。這兩種方法的區別是:prepare方法是將資源同步快取到記憶體中,一般載入本地較小的資源可以用這個,如果是較大的資源或者網路資源建議使用prepareAsync方法,非同步載入

實現效果如下所示:

3、MediaPlayer+SurfaceView+MediaController

第二種實現方式使用的是自定義控制元件,MediaPlayer+SurfaceView也可以使用系統自帶的MediaController控制器。

使用這個方式實現,佈局檔案只需一個SurfaceView即可,其他的控制元件都交給MediaController控制器,佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".MediaControllerTestActivity"
    android:id="@+id/root_ll">
    <SurfaceView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/controll_surfaceView"/>
</LinearLayout>

程式程式碼如下:

public class MediaControllerTestActivity extends Activity implements
        MediaController.MediaPlayerControl,
        MediaPlayer.OnBufferingUpdateListener,
        SurfaceHolder.Callback{
    private MediaPlayer mediaPlayer;
    private MediaController controller;
    private int bufferPercentage = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_controller_test);
        mediaPlayer = new MediaPlayer();
        controller = new MediaController(this);
        controller.setAnchorView(findViewById(R.id.root_ll));
        initSurfaceView();
    }

    private void initSurfaceView() {
        SurfaceView videoSuf = (SurfaceView) findViewById(R.id.controll_surfaceView);
        videoSuf.setZOrderOnTop(false);
        videoSuf.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        videoSuf.getHolder().addCallback(this);
    }
    @Override
    protected void onResume() {
        super.onResume();
        try {
            String path = Environment.getExternalStorageDirectory().getPath() + "/20180730.mp4";
            mediaPlayer.setDataSource(path);
            mediaPlayer.setOnBufferingUpdateListener(this);
            //mediaPlayer.prepare();

            controller.setMediaPlayer(this);
            controller.setEnabled(true);

        }catch (IOException e){
            e.printStackTrace();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mediaPlayer.isPlaying()){
            mediaPlayer.stop();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != mediaPlayer){
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        controller.show();
        return super.onTouchEvent(event);
    }

    //MediaPlayer
    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {

    }
    //MediaPlayerControl
    @Override
    public void onBufferingUpdate(MediaPlayer mediaPlayer, int i) {
        bufferPercentage = i;
    }

    @Override
    public void start() {
        if (null != mediaPlayer){
            mediaPlayer.start();
        }
    }

    @Override
    public void pause() {
        if (null != mediaPlayer){
            mediaPlayer.pause();
        }
    }

    @Override
    public int getDuration() {
        return mediaPlayer.getDuration();
    }

    @Override
    public int getCurrentPosition() {
        return mediaPlayer.getCurrentPosition();
    }

    @Override
    public void seekTo(int i) {
        mediaPlayer.seekTo(i);
    }

    @Override
    public boolean isPlaying() {
        if (mediaPlayer.isPlaying()){
            return true;
        }
        return false;
    }

    @Override
    public int getBufferPercentage() {
        return bufferPercentage;
    }

    @Override
    public boolean canPause() {
        return true;
    }

    @Override
    public boolean canSeekBackward() {
        return true;
    }

    @Override
    public boolean canSeekForward() {
        return true;
    }

    @Override
    public int getAudioSessionId() {
        return 0;
    }

    //SurfaceHolder.callback
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        mediaPlayer.setDisplay(surfaceHolder);
        mediaPlayer.prepareAsync();
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

    }
}

效果圖如下:

更多流媒體資訊,請關注小牛安卓乾貨鋪,將進行不定期推送。