Android 视频播放器笔记之播放器实例代码

本文详细介绍了如何在Android中使用MediaPlayer和SurfaceView实现视频播放功能,包括播放、暂停、显示进度以及全屏切换。讨论了SurfaceHolder的重要性和MediaPlayer的生命周期,并提供了关键代码示例,涉及进度条更新、媒体播放控制等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面有文章讲到android 实现视频播放的三种方式,本文将讲述以下使用MediaPlayer + SurfaceView方法实现的思路以及相关代码。因为它自定义的优点,所以实现起来会比较麻烦。如果只想实现播放功能可以参考前文:http://blog.csdn.net/annieliu502/article/details/39931577

本例是使用MediaPlayer来控制媒体的播放,暂停,进度等,使用SufaceView显示视频内容。

一、效果展示


二、功能介绍及代码实现

可以播放、暂停、显示进度,全屏/退出全屏;需要两个自定义控件来实现功能。

1、播放视频部分:自定义一个控件继承SurfaceView(显示视频内容),实现MediaPlayControl(控制视频播放)。

SurfaceHolder是用来监控SurfaceView的变化的;当SurfaceView变化的时候会触发SurfaceHolder.Callback的实现类中的一些方法;

mediaPlayer.setDisplay(holder)是关键,也可是mediaPlayer.setDisplay(surface);此方法设置哪个surfaceView显示视频,如果没有设置会导致视频只能播放声音而无法显示图像。使用mediaPlayer播放音频的时候就无需设置这个。下图展示了如何使用mediaPlayer:

此图摘自:【Android笔记】Android MediaPlayer的生命周期




上代码:

package com.dream.videoplayer_v4;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.widget.MediaController.MediaPlayerControl;

public class CutomerVideoView extends SurfaceView implements MediaPlayerControl {

	private static final String TAG = CutomerVideoView.class.getSimpleName();
	private MediaPlayer mediaPlayer;
	private SurfaceHolder surfaceHolder;// monitor changes to the surface
	private Callback surfaceCallBack = new Callback() {//receive the information about changes to the surface

		@Override
		public void surfaceDestroyed(SurfaceHolder holder) {
			// TODO Auto-generated method stub
			surfaceHolder = null;
			destroyPlayer();
		}

		@Override
		public void surfaceCreated(SurfaceHolder holder) {
			// TODO Auto-generated method stub
			try {
				surfaceHolder = holder;
				mediaPlayer = new MediaPlayer();
				mediaPlayer.setDisplay(holder);

			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		@Override
		public void surfaceChanged(SurfaceHolder holder, int format, int width,
				int height) {
			// TODO Auto-generated method stub

		}
	};

	private String uri;
	private OnPreparedListener onPreparedListener;

	public void setOnPreparedListener(OnPreparedListener onPreparedListener) {
		this.onPreparedListener = onPreparedListener;
	}

	public CutomerVideoView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
		initView();
	}

	public CutomerVideoView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		initView();
	}

	public CutomerVideoView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		initView();
	}

	@SuppressWarnings("deprecation")
	private void initView() {
		try {
			getHolder().addCallback(surfaceCallBack);
			getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public boolean canPause() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean canSeekBackward() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean canSeekForward() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public int getAudioSessionId() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int getBufferPercentage() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int getCurrentPosition() {
		if (mediaPlayer != null)
			return mediaPlayer.getCurrentPosition();

		return 0;
	}

	@Override
	public int getDuration() {
		if (mediaPlayer != null)
			return mediaPlayer.getDuration();

		return 0;
	}

	@Override
	public boolean isPlaying() {
		if (mediaPlayer != null)
			return mediaPlayer.isPlaying();

		return false;
	}

	@Override
	public void pause() {
		if (mediaPlayer != null) {
			mediaPlayer.pause();
		}
	}

	@Override
	public void seekTo(int pos) {
		if (mediaPlayer != null) {
			mediaPlayer.seekTo(pos);
		}
	}

	@Override
	public void start() {
		if (mediaPlayer != null) {
			mediaPlayer.start();
		}
	}

	private void destroyPlayer() {
		if (mediaPlayer != null) {
			mediaPlayer.reset();
			mediaPlayer.release();
			mediaPlayer = null;
		}
	}

	@SuppressLint("NewApi")
	public Bitmap createVideoThumbnail(String filePath) {
		Bitmap bm = null;
		MediaMetadataRetriever mmr = new MediaMetadataRetriever();
		mmr.setDataSource(filePath);
		bm = mmr.getFrameAtTime();
		return bm;
	}

	public void setVideoUri(String path) {
		this.uri = path;		
		openVideo();
	}

	public void openVideo() {		
		try {
			if(mediaPlayer==null){
				mediaPlayer=new MediaPlayer();
			}
			if(uri == null){
				uri=" ";
			}
			try {
				Log.i(TAG,"uri is :"+uri);
				mediaPlayer.setDataSource(getContext(), Uri.parse(uri));
			} catch (Exception e) {
				Log.i(TAG,"setDataSource fail");
				e.printStackTrace();
			}
			
			mediaPlayer.prepareAsync();
			mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
			mediaPlayer.setOnPreparedListener(onPreparedListener);

//			requestLayout();
//			invalidate();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

2、控制菜单栏(即有播放按钮、进度条、缩小按钮的那一栏)

1)进度条的更新

通过定义一个定时任务TimeTask来实现进度条的更新;

可以通过mediaPlayer.getCurrentPosition()来获取当前进度,通过mediaPlaer.getDuration()来获取总时长;

通过seekBar.seekTo();改变进度条进度。

值得注意的是,mediaPlaer.getDuration();方法只能在一定状态下使用才能得到正确的时长;

比如,Idle状态下调用getDuration()等方法是不正确的。通过new MediaPlayer()而进入Idle态不会报错,但也不会得到正确的时长,而通过reset()方法进入Idle态会导致mediaPlayer进入Error态;在Prepared态能得到正确的时长。

有兴趣的童鞋可以看一下MediaPlayer的状态图,可以避免开发中的很多错误:http://blog.csdn.net/ddna/article/details/5178864


2)进度条改变更新mediaPlayer进度

2.1)使用OnSeekBarChangeListener监听SeekBar进度的改变。

2.2)使用mediaPlayer.seekTo();改变视频播放进度;

上代码:

FullScreenActivity:

package com.dream.videoplayer_v4;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;

public class FullScreenActivity extends Activity {

	protected static final String TAG = FullScreenActivity.class.getSimpleName();
	public CutomerVideoView videoView;
	private CustomMediaController mediaController;
	private ImageView iv;
	private ImageView playBtn;
	private TextView progressTv;
	private int duration;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
		setContentView(R.layout.full_screen_video_player);

		initView();
	}

	private void initView() {
		videoView = (CutomerVideoView) findViewById(R.id.videoview_fullscreen);
		videoView.setOnPreparedListener(new OnPreparedListener() {

			@Override
			public void onPrepared(MediaPlayer mp) {
				Log.i(TAG,"enter onPrepared");
				mp.setLooping(false);
								
				duration=mp.getDuration();

				if(duration>0){
					handler.sendEmptyMessage(1);
				}
				
				if(mediaController.isPlaying){
					mp.start();
				}
			}
		});
		videoView.setVideoUri(MainActivity.URL);
		mediaController = (CustomMediaController) findViewById(R.id.mediacontroller_fullscreen);
		Timer timer=new Timer();
		timer.schedule(timeTask, 0, 1000);
		
		iv = (ImageView) findViewById(R.id.iv_fullscreen);
		iv.setImageBitmap(videoView.createVideoThumbnail(MainActivity.URL));

		playBtn = (ImageView) findViewById(R.id.playBtn);
		playBtn.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {				
				doPlay();
			}			
		});
		
		progressTv=(TextView)findViewById(R.id.progressTv);
	
	}

	private void setProgressTv(int duration, int pos) {
		progressTv.setText(Util.parseMillionToTime(pos)+"/"+Util.parseMillionToTime(duration));
	}
	
	public void doPlay() {
		playBtn.setVisibility(View.GONE);
		if(iv.getVisibility()==View.VISIBLE){
			iv.setVisibility(View.GONE);		
			videoView.openVideo();
		}else{
			videoView.start();
		}
		
		mediaController.playOrPauseBtn.setBackgroundResource(R.drawable.pause_btn_selector);
		mediaController.isPlaying=true;
	}
	
	public void doPause(){
		playBtn.setVisibility(View.VISIBLE);
		videoView.pause();
		mediaController.isPlaying=false;
	}

	TimerTask timeTask = new TimerTask() {

		@Override
		public void run() {
			try {
				if(videoView==null || !videoView.isPlaying())
					return;
				if(!mediaController.isPressSeekBar()){
					handler.sendEmptyMessage(0);
				}
				if(videoView.getCurrentPosition()==duration && duration>0){
					handler.sendEmptyMessage(0);
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	};

	private Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case 0:
				changeSeekInfo();
				break;
			case 1:
				Log.i("info","duration is : "+duration);
				setProgressTv(duration, 0);				
				break;
			}
			
		}

		private void changeSeekInfo() {
			try {
				int position = videoView.getCurrentPosition();
				if(duration>0){					
					mediaController.seekTo(position, duration);
					setProgressTv(duration, position);
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		};
	};

}


CustomMediaController(菜单栏):

package com.dream.videoplayer_v4;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.view.View.OnClickListener;

public class CustomMediaController extends FrameLayout implements OnClickListener{
	private static final String TAG = CustomMediaController.class.getSimpleName();
	public ImageButton playOrPauseBtn;
	public ImageButton fullOrNotBtn;
	private SeekBar progressBar;
	
	public boolean isPlaying=false;
	private FullScreenActivity mAct;
	int mProgress;

	public CustomMediaController(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
		initView(context);
	}

	public CustomMediaController(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		initView(context);
	}

	public CustomMediaController(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		initView(context);
	}
	
	private void initView(Context context){
		mAct=(FullScreenActivity)context;
		View view=View.inflate(context, R.layout.media_controller, null);
		playOrPauseBtn=(ImageButton)view.findViewById(R.id.pauseOrPlay);
		fullOrNotBtn=(ImageButton)view.findViewById(R.id.fullOrNot);
		progressBar=(SeekBar)view.findViewById(R.id.progressBar);
		
		playOrPauseBtn.setOnClickListener(this);
		fullOrNotBtn.setOnClickListener(this);
		
		progressBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
			
			@Override
			public void onStopTrackingTouch(SeekBar seekBar) {
				mAct.videoView.seekTo(mProgress);
			}
			
			@Override
			public void onStartTrackingTouch(SeekBar seekBar) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onProgressChanged(SeekBar seekBar, int progress,
					boolean fromUser) {
				mProgress=progress*mAct.videoView.getDuration()/seekBar.getMax();
			}
		});

		this.addView(view);
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.pauseOrPlay:
			doPauseOrPlay();
			break;

		case R.id.fullOrNot:
			doFullOrNot();
			break;
		}
	}

	private void doPauseOrPlay() {
		if(isPlaying){
			playOrPauseBtn.setBackgroundResource(R.drawable.play_btn_selector);
			isPlaying=false;
			mAct.doPause();
		}else{
			playOrPauseBtn.setBackgroundResource(R.drawable.pause_btn_selector);
			isPlaying=true;
			mAct.doPlay();
		}
	}
	
	private void doFullOrNot() {
		mAct.finish();
	}
	
	public void seekTo(int position,int duration){
		mProgress=progressBar.getMax()*position/duration;
		progressBar.setProgress(mProgress);
	}
	
	public boolean isPressSeekBar(){
		if(progressBar.isPressed()){
			return true;
		}
		return false;
	}
}



三、资料

MediaPlayer API 文档:http://www.cnblogs.com/over140/archive/2012/09/06/2673460.html

MediaPlayer 生命周期介绍:http://blog.csdn.net/ddna/article/details/5178864

MediaPlayer  使用方式:http://blog.csdn.net/ddna/article/details/5176233



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值