android視頻播放

前一段時間做視頻播放的功能,寫一下demo來記錄一下。

播放使用的surferview 配合mediaplayer 。Surfaceview來播放畫面,Mediaplayer來播放音頻。通過surfaceview的getHolder方法來獲取surfaceHolder。設置給mediaplayer就可以同時播放視頻音頻。
話不多說,直接上代碼和demo,項目中使用的,基本穩定。需要注意的要時刻注意mediaplayer的各種狀態之間的關係。

播放工具類

import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.SeekBar;

import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 視頻播放
 */
public class VideoPlayer implements MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener,
        SurfaceHolder.Callback, MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener {

    private String TAG = "VideoPlayer";
    private int mvideoWidth;
    private int mvideoHeight;
    public MediaPlayer mediaPlayer;
    private SurfaceView mSurfaceView;
    private SurfaceHolder surfaceHolder;
    private SeekBar skbProgress;
    private Timer mTimer = new Timer();
    private PlayCallBackListener mCallBack;
    private boolean mIsPrepare;
    private Context mContext;
    private int mVideoHeight;
    private int mVideoWidth;


    //給activity的回掉接口
    public interface PlayCallBackListener {
        void mediaInitCompletion();//完成初始化

        void playCompletion();//播放完成

        void starBufferring();//播放暫停,開始緩衝

        void endBufferring();//緩衝結束,開始播放

        void isPrepare(boolean res);//prepare成功

        void updateTime(int time);

        void videoError();//prepare失敗

        void haveError();

    }

    /**
     * 視頻播放構造
     */
    public VideoPlayer(Context context, SurfaceView surfaceView, SeekBar skbProgress, PlayCallBackListener callBack) {
        mCallBack = callBack;
        this.mContext = context;
        mSurfaceView = surfaceView;
        this.skbProgress = skbProgress;
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mTimer.schedule(mTimerTask, 0, 500);

    }


    /**
     * 定時器更新進度
     **/
    TimerTask mTimerTask = new TimerTask() {
        @Override
        public void run() {
            if (mediaPlayer == null)
                return;
            try {
                if (mIsPrepare && mediaPlayer.isPlaying() && skbProgress.isPressed() == false) {
                    handleProgress.sendEmptyMessage(0);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    Handler handleProgress = new Handler() {
        public void handleMessage(Message msg) {
            if (!mIsPrepare) {
                return;
            }
            if (mediaPlayer != null) {

                int duration = mediaPlayer.getDuration();
                int position = mediaPlayer.getCurrentPosition();
                if (duration > 0) {
                    mCallBack.updateTime(position);
                    long pos = skbProgress.getMax() * position / duration;
                    skbProgress.setProgress((int) pos);
                }
            }
        }
    };


    /**
     * 設置完URI只有自動播放
     */
    public void playUrl(String videoUrl) {
        try {
            mediaPlayer.reset();
            mediaPlayer.setDataSource(videoUrl);
            mediaPlayer.prepare();
            setVideoSize();
            mCallBack.isPrepare(true);
            mIsPrepare = true;
        } catch (Exception e) {
            mIsPrepare = false;
            mCallBack.videoError();
            e.printStackTrace();
        }
    }

    /**
     * 調整視頻播放的大小,防止全屏變形
     */
    private void setVideoSize() {
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        int screenWidth = outMetrics.widthPixels;
        ViewGroup.LayoutParams layoutParams = mSurfaceView.getLayoutParams();
        layoutParams.width = screenWidth;
        layoutParams.height = (int) (mediaPlayer.getVideoHeight() * (screenWidth * 1.0) / mediaPlayer.getVideoWidth());
        mSurfaceView.setLayoutParams(layoutParams);
    }

    /**
     * 暫停
     */
    public void pause() {
        if (mediaPlayer != null && mIsPrepare) {
            mediaPlayer.pause();
        }
    }

    /**
     * 播放(暫停以後繼續播放)
     */
    public void play() {
        if (mIsPrepare) {
            mediaPlayer.start();
        }
    }

    /**
     * 停止
     */
    public void stop() {
        skbProgress.setProgress(100);
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

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

    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
        try {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setDisplay(surfaceHolder);
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mediaPlayer.setOnBufferingUpdateListener(this);
            mediaPlayer.setOnCompletionListener(this);
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.setOnErrorListener(this);
            mediaPlayer.setOnInfoListener(this);
        } catch (Exception e) {
            mCallBack.haveError();
            e.printStackTrace();
        }
        mCallBack.mediaInitCompletion();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {//播放完成.出錯 退出,都在這釋放資源。
        if (mediaPlayer != null) {
            mIsPrepare = false;
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }


    @Override
    /**
     * 在進入 Prepared 狀態 並 開始播放的時候回調;
     */
    public void onPrepared(MediaPlayer arg0) {
        mvideoWidth = mediaPlayer.getVideoWidth();
        mvideoHeight = mediaPlayer.getVideoHeight();
        if (mvideoHeight != 0 && mvideoWidth != 0) {
            arg0.start();
        }
    }

    /**
     * 播放完成
     */
    @Override
    public void onCompletion(MediaPlayer arg0) {
        skbProgress.setProgress(100);
        mediaPlayer.reset();
        mCallBack.playCompletion();
    }

    @Override
    public void onBufferingUpdate(MediaPlayer arg0, int bufferingProgress) {
        skbProgress.setSecondaryProgress(bufferingProgress);
        if (mediaPlayer.getDuration() > 0) {
            int currentProgress = skbProgress.getMax() * mediaPlayer.getCurrentPosition() / mediaPlayer.getDuration();
            Log.e(TAG, currentProgress + "% play" + bufferingProgress + "% buffer");
        }
    }

    /**
     * 播放出錯
     */
    @Override
    public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {//錯誤
        if (mediaPlayer.isPlaying()) {
            mediaPlayer.stop();
        }
        mediaPlayer.reset();
        mCallBack.haveError();
        return true;
    }

    @Override
    public boolean onInfo(MediaPlayer mediaPlayer, int what, int i1) {
        switch (what) {
            case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
                Log.e(TAG, "開始渲染第一幀");
                break;
            case MediaPlayer.MEDIA_INFO_BUFFERING_START:
                Log.e(TAG, "暫停播放開始緩衝更多數據");
                mCallBack.starBufferring();
                break;
            case MediaPlayer.MEDIA_INFO_BUFFERING_END:
                Log.e(TAG, "緩衝了足夠的數據重新開始播放");
                mCallBack.endBufferring();
                break;
        }
        return false;
    }

}

其中設置視頻大小那個方法setVideoSize()是防止全屏變形,按比例來設置的大小。

activity代碼

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements View.OnClickListener {
    private SurfaceView mSurfaceView;
    private ImageView mIvPsudr;//播放暫停按鈕
    private SeekBar mSkbProgress;//進度條
    private VideoPlayer mPlayer;//視頻播放的工具類
    private String murl = "/storage/sdcard0/test.mp4";//視頻路徑可以是網絡或者本地的
    private boolean mIsPrepare;//是否初始化成功
    private boolean misPause;//暫停標誌位
    private TextView mTvNowTime, mTvToatalTime;//視頻時間顯示
    private ProgressBar mProgressbar;
    private Button mRePlayBTN;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        setContentView(R.layout.activity_main);
        initView();
        initPlayer();
    }

    /**
     * 構建播放器
     */
    private void initPlayer() {
        if (murl != null || !TextUtils.isEmpty(murl)) {
            mPlayer = new VideoPlayer(this, mSurfaceView, mSkbProgress, mlistener);
        } else {
            Toast.makeText(this, "視頻路徑無效", Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    /**
     * 初始化View
     */
    private void initView() {
        mRePlayBTN = (Button) findViewById(R.id.btn_replay);
        mRePlayBTN.setOnClickListener(this);
        mProgressbar = (ProgressBar) findViewById(R.id.pb_wait);
        mTvNowTime = (TextView) findViewById(R.id.tv_now_time);
        mTvToatalTime = (TextView) findViewById(R.id.tv_total_time);
        mSkbProgress = (SeekBar) this.findViewById(R.id.skbProgress);
        mSurfaceView = (SurfaceView) this.findViewById(R.id.surfaceView1);
        mIvPsudr = (ImageView) this.findViewById(R.id.iv_pause);
        mIvPsudr.setOnClickListener(this);
        mSkbProgress.setOnSeekBarChangeListener(new SeekBarChangeEvent());
    }


    @Override
    public void onClick(View arg0) {
        int id = arg0.getId();
        if (id == R.id.iv_pause) {
            if (!mIsPrepare)
                return;
            if (mIsPrepare && mPlayer.mediaPlayer.isPlaying()) {
                mPlayer.pause();
                misPause = true;
                mIvPsudr.setImageResource(R.drawable.videplay);
            } else {
                if (misPause) {
                    mPlayer.play();
                    misPause = false;
                    mIvPsudr.setImageResource(R.drawable.videopause);
                }
            }
        } else if (id == R.id.btn_replay) {
            mRePlayBTN.setVisibility(View.GONE);
            mPlayer.playUrl(murl);
        }
    }

    /**
     * 進度條改變的監聽
     */
    class SeekBarChangeEvent implements SeekBar.OnSeekBarChangeListener {
        int progress;

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (seekBar.getMax() > 0 && mIsPrepare)//已經設置播放源才能獲取時間
                this.progress = progress * mPlayer.mediaPlayer.getDuration() / seekBar.getMax();
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            if (mIsPrepare)
                mPlayer.mediaPlayer.seekTo(progress);
        }
    }

    /**
     * 視頻播放的回掉監聽
     */
    VideoPlayer.PlayCallBackListener mlistener = new VideoPlayer.PlayCallBackListener() {
        @Override
        public void mediaInitCompletion() {//init完成
            mProgressbar.setVisibility(View.GONE);
            mPlayer.playUrl(murl);
        }

        @Override
        public void playCompletion() {
            //播放完成
            mIsPrepare = false;
            mRePlayBTN.setVisibility(View.VISIBLE);
        }

        @Override
        public void starBufferring() {
            //開始緩衝
            mProgressbar.setVisibility(View.VISIBLE);
        }

        @Override
        public void endBufferring() {
            //緩衝足夠開始播放
            mProgressbar.setVisibility(View.GONE);
        }

        @Override
        public void isPrepare(boolean res) {
            //初始化完成,顯示視頻長度
            int time = mPlayer.mediaPlayer.getDuration();
            mIsPrepare = res;
            int second = time / 1000 % 60;
            String secondStr;
            String min = String.valueOf(time / 1000 / 60);
            if (second < 10) {
                secondStr = "0" + second;
            } else
                secondStr = String.valueOf(second);
            mTvToatalTime.setText(min + ":" + secondStr);
        }

        @Override
        public void updateTime(int time) {
            //更新進度
            String min = String.valueOf(time / 1000 / 60);
            int second = time / 1000 % 60;
            String secondStr;
            if (second < 10) {
                secondStr = "0" + second;
            } else
                secondStr = String.valueOf(second);
            mTvNowTime.setText(min + ":" + secondStr);
        }

        @Override
        public void videoError() {
            //mediaPlayer 初始化出錯(一般爲視頻 路徑,格式等問題)
            Toast.makeText(MainActivity.this, "視頻無效", Toast.LENGTH_SHORT).show();
//            finish();
        }

        @Override
        public void haveError() {
            //播放發生錯誤
            if (mToast == null) {
                mToast = Toast.makeText(MainActivity.this, "播放失敗", Toast.LENGTH_SHORT);
            }
            mToast.show();
            finish();
        }
    };

    private Toast mToast;


    @Override
    public void finish() {
        getWindow().clearFlags(WindowManager.LayoutParams.FILL_PARENT);
        super.finish();
    }

    //按下back按鍵,先停止播放,後finish
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
            if (mIsPrepare)
                mPlayer.mediaPlayer.stop();
            finish();
            return true;
        }
        return false;
    }

}

activity 佈局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/black">

        <SurfaceView
            android:id="@+id/surfaceView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true" />
    </RelativeLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#A0000000"
        android:gravity="center"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/iv_pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="5dp"
            android:padding="10dp"
            android:src="@drawable/videopause"
            android:text="暫停" />

        <TextView
            android:id="@+id/tv_now_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white" />

        <SeekBar
            android:id="@+id/skbProgress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1.0"
            android:max="100"
            android:paddingLeft="10dip"
            android:paddingRight="10dip" />


        <TextView
            android:id="@+id/tv_total_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white" />
    </LinearLayout>

    <ProgressBar
        android:id="@+id/pb_wait"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

    <Button
        android:id="@+id/btn_replay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="重播"
        android:visibility="gone" />
</RelativeLayout>

佈局很簡單。播放工具類採用接口回掉方式來給activity來回掉各種狀態。

demo下載地址http://download.csdn.net/detail/spinchao/9523242

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章