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设置一个callback,callback的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) {

}
}

FormatTimeUtil代码:

/**
* 取消科学计数法,原样输出double数值
* */
public class FormatTimeUtil {

public FormatTimeUtil(){}

/**
* Formats the time to look like “HH:MM:SS”
*
* @param millis The number of elapsed milliseconds
* @return A formatted time value
*/
public static String formatLongToTimeStr(final long millis) {
//TODO does not support hour>=100 (4.1 days)
return String.format(“%02d:%02d:%02d”,
millis / (1000 * 60 * 60),
(millis / (1000 * 60)) % 60,
(millis / 1000) % 60
);
}

}

注意事项:MediaPlayer有prepare和prepareAsync两种方法。这两种方法的区别是: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) {

}
}

效果图如下:

 

 

 

转自:https://blog.csdn.net/qq_19688207/article/details/109490631