在Android中,在做视频播放的时候,我们可以直接使用Android原生的VideoView来实现,也可以使用SurfaceView+MediaPlayer来实现,本文主要针对这两种方式进行实现。
主要代码有:
设置VideoView的url和MediaController,然后调用start()方法,即可播放视频
videoView.setMediaController(new MediaController(this));
videoView.setVideoPath(videoInfo.getFilePath());
videoView.start();
可以看到非常简单,只需要短短的三行代码,就可以实现本地视频和网络视频的播放。
当然VideoView还提供一些控制视频播放的方法
如
pause() //让视频暂停
start() //播放开始播放
stop() //停止播放
以及一些监听方法
//缓冲进度的监听
//缓冲进度的监听
videoView.setOnPreparedListener(new MyPlayOnPreparedListener());
//播放完成回调
videoView.setOnCompletionListener( new MyPlayerOnCompletionListener());
class MyPlayerOnCompletionListener implements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
Toast.makeText( VideoViewActivity.this, "播放完成了", Toast.LENGTH_SHORT).show();
}
}
class MyPlayOnPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
}
}
使用VideoView播放视频如果视频的分辨率小于设备的屏幕分辨率,VideoVIew在播放视频的时候都是在左上角显示的,比较影响美观,解决办法也很简单,只需要在VideoView的外层嵌套一个相对布局同时设置VideoView的layout_centerInParent=”true”就可以了。
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<VideoView
android:id="@+id/videoview_view"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
RelativeLayout>
效果如下:
首先是获取了本地的视频列表,左滑的话可以选择网络视频进行播放,只是加载网络视频需要费点时间,就不演示了,点击后用VideoView进行播放,可以看到VideoView本身提供了进度条,暂停,快进等功能,对于视频要求不是太大的情况下可以选择使用这种方式,使用起来比较简单
下面是完整的代码,
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button btVideo;
private Button btSurface;
private Button btTexture;
private TextView tvTitle;
private ViewPager viewPager;
private TabLayout tableLayout;
private String[] attr=new String[]{"本地视频","网络视频"};
private List fragments;
private FileFragment fileFragment;
private NetFragment netFragment;
private ViewPagerAdapter adapter;
public static int flag=0;
public static final int VIDEOVIEW_FLAG=0;
public static final int SURFACEVIEW_FLAG=1;
public static final int TEXTUREVIEW_FLAG=2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
fragments=new ArrayList<>();
fileFragment=new FileFragment();
netFragment=new NetFragment();
fragments.add(fileFragment);
fragments.add(netFragment);
tableLayout.addTab(tableLayout.newTab().setText(attr[0]));
tableLayout.addTab(tableLayout.newTab().setText(attr[1]));
adapter=new ViewPagerAdapter(getSupportFragmentManager(),this,attr,fragments);
viewPager.setAdapter(adapter);
tableLayout.setupWithViewPager(viewPager);
}
private void initView() {
btVideo= (Button) findViewById(R.id.main_video);
btVideo.setOnClickListener(this);
btSurface= (Button) findViewById(R.id.main_surface);
btSurface.setOnClickListener(this);
btTexture= (Button) findViewById(R.id.main_texture);
btTexture.setOnClickListener(this);
tvTitle= (TextView) findViewById(R.id.main_title);
viewPager= (ViewPager) findViewById(R.id.main_viewpager);
tableLayout= (TabLayout) findViewById(R.id.main_tab);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.main_video:
Toast.makeText(this,"切换到VideoVIew",Toast.LENGTH_SHORT).show();
flag=VIDEOVIEW_FLAG;
break;
case R.id.main_surface:
Toast.makeText(this,"切换到SurfaceView",Toast.LENGTH_SHORT).show();
flag=SURFACEVIEW_FLAG;
break;
case R.id.main_texture:
Toast.makeText(this,"切换到TextureView",Toast.LENGTH_SHORT).show();
flag=TEXTUREVIEW_FLAG;
break;
}
}
}
MainActivity就是TabLayout+ViewPager 切换本地和网络视频,下面三个按钮是切换播放器。
public class FileFragment extends Fragment {
private List mData;
private VideoInfoAdapter adapter;
private RecyclerView recyclerView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fm_file,container,false);
initView(view);
initData();
return view;
}
private void initView(View view) {
recyclerView= view.findViewById(R.id.main_recycler);
}
private void initData() {
mData=new ArrayList<>();
String[] attr=new String[]{
MediaStore.MediaColumns.DATA,
BaseColumns._ID,
MediaStore.MediaColumns.TITLE,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.Video.VideoColumns.DURATION,
MediaStore.MediaColumns.SIZE
};
Cursor cursor=getActivity().getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,attr,
null,null,null);
if (cursor!=null){
while (cursor.moveToNext()){
VideoInfo info=new VideoInfo();
info.setFilePath(cursor.getString(cursor
.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)));
info.setMimeType(cursor.getString(cursor
.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)));
info.setTitle(cursor.getString(cursor
.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE)));
info.setTime(CommTools.LongToHms(cursor.getInt(cursor
.getColumnIndexOrThrow(MediaStore.Video.VideoColumns.DURATION))));
info.setSize(CommTools.LongToPoint(cursor
.getLong(cursor
.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))));
int id = cursor.getInt(cursor
.getColumnIndexOrThrow(BaseColumns._ID));
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
info.setB(MediaStore.Video.Thumbnails.getThumbnail(getActivity().getContentResolver(), id,
MediaStore.Images.Thumbnails.MICRO_KIND, options));
mData.add(info);
}
}
adapter=new VideoInfoAdapter(getActivity(),mData);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
adapter.setOnItemClickListener(new VideoInfoAdapter.OnItemClickListener() {
@Override
public void onItemClick(VideoInfo videoInfo,int position) {
Intent intent=new Intent();
intent.putExtra("VIDEO_INFO",videoInfo);
intent.putExtra("VIDEO_SORT",position+"/"+mData.size());
intent.putExtra("VIDEO_TYPE",0);
switch (MainActivity.flag){
case MainActivity.VIDEOVIEW_FLAG:
intent.setClass(getActivity(), VideoViewActivity.class);
startActivity(intent);
break;
case MainActivity.SURFACEVIEW_FLAG:
intent.setClass(getActivity(), SurfaceActivity.class);
startActivity(intent);
break;
case MainActivity.TEXTUREVIEW_FLAG:
break;
}
}
});
}
}
主要是获取本地的视频列表集合,然后用一个RecyclerView显示。
虽然使用ViedoView比较简单,但是如果遇上比较复杂的布局效果,自定义程度较高的话,就要用到SurfaceView+MediaPlayer这种了,使用这种方式进行视频播放可以更加灵活,使用SurfaceView进行显示,MediaPlayer和SurfaceView进行绑定,就可以显示出完整的视频了。
关于SurfaceView,从android 1.0就有了,一般来说,UI对刷新都需要在UI线程中完成,但是,surfaceview可以在非UI线程中完成刷新。拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。
大神的链接,感兴趣的可以去了解一下
Android视图SurfaceView的实现原理分析
怎么使用,首先我们先设置需要播放的资源,
可以使文件、文件路径、或者URL。
mediaPlayer.setDataSource(url);
然后设置SurfaceHolder,需要先创建SurfaceHolder,可以通过surfaceView.getHolder()取得,
holder=surfaceView.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mediaPlayer.setDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
同时监听surfaceHolder,在surfaceholder被创建的时候,与MediaPlayer进行绑定
然后调用MediaPlayer.prepare()来准备。
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(final MediaPlayer mediaPlayer) {
/**装载完成,开始播放*/
mediaPlayer.start();
}
}
一般我们选择用异步的方式进行装载,然后在onPrePared方法里调用开始播放
下面是全部的代码,对SurfaceView进行了简单的封装,包括暂停,开始,播放进度,设置播放地址,全屏和半屏切换,状态栏的显示和隐藏等,
下面是完整的代码:
public class MysurfaceView extends SurfaceView implements
MediaPlayer.OnErrorListener
,MediaPlayer.OnCompletionListener
,MediaPlayer.OnVideoSizeChangedListener
,SurfaceHolder.Callback{
private static final String TAG=MysurfaceView.class.getSimpleName();
private MediaPlayer mediaPlayer;
private SurfaceHolder holder;
/**视频播放的Url*/
private String url;
/**播放状态*/
private boolean isPlay;
/**横竖屏标识*/
private boolean screenDirection=true;
/**视频的宽高*/
private float videoHeight;
private float videoWidth;
/**系统屏幕的宽高*/
private float systemWidth;
private float systemHeight;
/**控件的宽高*/
private float surWidth;
private float surHeight;
private Context context;
public MysurfaceView(Context context) {
super(context);
init(context);
}
public MysurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MysurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public interface OnVideoPlayingListener{
void onVideoSizeChanged(int vWidth, int vHeight);
void onPlaying(int duration, int percent);
void onStart();
void onPlayOver();
void onVideoSize(int videoSize);
}
private OnVideoPlayingListener listener;
public void setOnVideoPlayingListener(OnVideoPlayingListener listener){
this.listener=listener;
}
/**设置监听*/
private void initEvent() {
/**注册当surfaceView创建、改变和销毁时应该执行的方法*/
holder.addCallback(this);
/**播放出错时的监听*/
mediaPlayer.setOnErrorListener(this);
/**播放结束时的监听*/
mediaPlayer.setOnCompletionListener(this);
/**视频尺寸的监听*/
mediaPlayer.setOnVideoSizeChangedListener(this);
}
/**初始化*/
private void init(Context context) {
this.context=context;
mediaPlayer=new MediaPlayer();
holder=this.getHolder();
/**
* 这里必须设置为SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS哦,意思
* 是创建一个push的'surface',主要的特点就是不进行缓冲
*/
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
DisplayMetrics dm = new DisplayMetrics();
((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(dm);
systemWidth = dm.widthPixels;
systemHeight=dm.heightPixels;
initEvent();
}
/**设置全屏播放*/
public void setFullScreen(){
hideNavigationBar();
((Activity)context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
scaleChangeSize(systemHeight,systemWidth);
}
/**恢复半屏播放*/
public void setHalfScreen(){
showNavigationBar();
((Activity)context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
scaleChangeSize(surWidth,surHeight);
}
/**显示状态栏*/
private void showNavigationBar(){
View decorView =((Activity)context). getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
/**隐藏状态栏*/
public void hideNavigationBar() {
int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN; // hide status bar
((Activity)context).getWindow().getDecorView().setSystemUiVisibility(uiFlags);
}
/**设置视频播放路径*/
public void setUrl(String url){
this.url=url;
}
/**暂停播放和继续播放*/
public void pause() {
if (mediaPlayer!=null){
if (mediaPlayer.isPlaying()&&isPlay==true){
mediaPlayer.pause();
}else {
mediaPlayer.start();
}
}
}
/**停止播放*/
public void stop(){
mediaPlayer.stop();
}
/**指定位置播放*/
public void seekTo(int progress){
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
/**设置当前播放的位置*/
mediaPlayer.seekTo(progress);
}
}
/**销毁 回收资源*/
public void finishVideo(){
mediaPlayer.stop();
mediaPlayer.release();
}
/**等比例缩放视频*/
public void scaleChangeSize(float width,float height){
float xsca=width/videoWidth;
float ysca=height/videoHeight;
float r=min(xsca,ysca);
float w=videoWidth*r;
float h=videoHeight*r;
ViewGroup.LayoutParams params= getLayoutParams();
params.width= (int) w;
params.height= (int) h;
setLayoutParams(params);
}
/**开始播放*/
public void play(){
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mediaPlayer.setDataSource(url);
/**异步装载*/
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(final MediaPlayer mediaPlayer) {
/**等比例缩放视频尺寸*/
videoWidth=mediaPlayer.getVideoWidth();
videoHeight=mediaPlayer.getVideoHeight();
surWidth=getWidth();
surHeight=getHeight();
scaleChangeSize(surWidth,surHeight);
mediaPlayer.start();
listener.onVideoSize(mediaPlayer.getDuration());
new Thread() {
@Override
public void run() {
try {
isPlay = true;
while (isPlay) {
int current = mediaPlayer.getCurrentPosition();
Message message=Message.obtain();
message.what=1;
message.obj=current;
handler.sendMessage(message);
sleep(500);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
isPlay=true;
listener.onStart();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
if (listener != null ) {
listener.onPlaying(mediaPlayer.getDuration(), (Integer) msg.obj);
sendEmptyMessageDelayed(0, 1000);
}
}
}
};
/**播放结束的监听*/
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
isPlay=false;
listener.onPlayOver();
}
/**播放错误的监听*/
@Override
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
isPlay=false;
return false;
}
/**视频尺寸的监听*/
@Override
public void onVideoSizeChanged(MediaPlayer mediaPlayer, int i, int i1) {
int videoW=mediaPlayer.getVideoWidth();
int videoH=mediaPlayer.getVideoHeight();
if (listener!=null){
listener.onVideoSizeChanged(videoW,videoH);
}
}
/**SurfaceHolder被创建*/
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mediaPlayer.setDisplay(holder);
}
/**SurfaceHolder被改变*/
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {}
/**SurfaceHolder被销毁*/
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {}
}
上面的注释都十分的详细,应该都可以看懂,对视频拉伸也做了处理,下面是Activity的代码:
public class TestSurfaceActivity extends AppCompatActivity implements View.OnClickListener{
private Intent intent;
private VideoInfo videoInfo;
private ImageView imgPlay;
private ImageView imgBack;
private SeekBar seekBar;
private TextView tvTotalTime;
private TextView tvPlayTime;
private ImageView ivAll;
private MysurfaceView mysurfaceView;
private boolean isFull;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_surface);
initView();
initData();
initEvent();
}
private void initEvent() {
mysurfaceView.setOnVideoPlayingListener(new MysurfaceView.OnVideoPlayingListener() {
@Override
public void onVideoSizeChanged(int vWidth, int vHeight) {
}
@Override
public void onPlaying(int duration, int percent) {
Log.i("surface","播放进度"+"总时长"+duration+" 当前播放进度"+percent);
seekBar.setMax(duration);
seekBar.setProgress(percent);
tvPlayTime.setText(CommTools.LongToHms(percent));
}
@Override
public void onStart() {
Toast.makeText(TestSurfaceActivity.this,"开始播放",Toast.LENGTH_SHORT).show();
}
@Override
public void onPlayOver() {
finish();
}
/**播放总时长*/
@Override
public void onVideoSize(int videoSize) {
tvTotalTime.setText(CommTools.LongToHms(videoSize));
seekBar.setMax(videoSize);
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
/**当进度条停止修改的时候触发*/
/**取得当前进度条的刻度*/
int progress = seekBar.getProgress();
/**设置当前播放的位置*/
mysurfaceView.seekTo(progress);
tvPlayTime.setText(""+CommTools.LongToHms(progress));
}
});
}
private void initData() {
intent=getIntent();
videoInfo=intent.getParcelableExtra("VIDEO_INFO");
mysurfaceView.setUrl(videoInfo.getFilePath());
}
private void initView() {
imgBack= (ImageView) findViewById(R.id.test_sur_iv_back);
imgBack.setOnClickListener(this);
imgPlay= (ImageView) findViewById(R.id.test_sur_iv_play);
imgPlay.setOnClickListener(this);
ivAll= (ImageView) findViewById(R.id.test_sur_iv_full);
ivAll.setOnClickListener(this);
seekBar= (SeekBar) findViewById(R.id.test_sur_seekbar);
tvTotalTime= (TextView) findViewById(R.id.test_sur_tv_total_time);
tvPlayTime= (TextView) findViewById(R.id.test_sur_tv_start_time);
mysurfaceView= (MysurfaceView) findViewById(R.id.test_sur_view);
}
@Override
protected void onResume() {
super.onResume();
mysurfaceView.setUrl(videoInfo.getFilePath());
mysurfaceView.play();
}
@Override
public void finish() {
super.finish();
mysurfaceView.finishVideo();
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.test_sur_iv_back:
finish();
break;
case R.id.test_sur_iv_play:
mysurfaceView.pause();
break;
case R.id.test_sur_iv_full:
isFull();
break;
}
}
public void isFull(){
if (isFull){
mysurfaceView.setHalfScreen();
isFull=false;
}else {
mysurfaceView.setFullScreen();
isFull=true;
}
}
}
主要是对当前的播放时间和进度,进行实时监听,同时还有全屏切换的功能,
主要功能就是这些的,
下面是效果图
因为是模拟器,所以切横竖屏的时候屏幕会变成横向,演示的效果不会太好,
下面是完整的Demo:
http://download.csdn.net/download/tzl0322/9941551
有分的支持一下,没分的可以到我的GitHub上下载:
https://github.com/ZhiLiangT/AndroidVideo