前一段時間做視頻播放的功能,寫一下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來回掉各種狀態。