在Android中,在做視頻播放的時候,我們可以直接使用Android原生的VideoView來實現,也可以使用SurfaceView+MediaPlayer來實現,本文主要針對這兩種方式進行實現。
一.VideoView實現
主要代碼有:
設置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<Fragment> 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<VideoInfo> 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顯示。
二.SurfaceView+MediaPlayer
雖然使用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