Android开发笔记(一百二十五)自定义视频播放器

视频播放方式

在Android中播放视频的方式有两种:
1、使用MediaPlayer结合SurfaceView进行播放。其中通过SurfaceView显示视频的画面,通过MediaPlayer来设置播放参数、并控制视频的播放操作;该方式的具体说明参见《 Android开发笔记(五十七)录像录音与播放》。
该方式的好处是灵活性强,可随意定制。缺点是编码复杂,连开始/暂停的按钮都要自己实现。
2、使用VideoView结合MediaController进行播放。VideoView其实是从SurfaceView扩展而来,并在内部集成了MediaPlayer,从而实现视频画面与视频操作的统一管理;而MediaController则是一个简单的播放控制条,它实现了基本的控制按钮,如开始/暂停按钮、上一个/下一个按钮、快进/快退按钮,以及进度条等控件;把VideoView与MediaController关联起来,便是一个类似于Window Media Player的精简版播放器。
该方式的好处是简单易用,编码容易。缺点是可定制差,难以扩展,想给按钮换个样式都不行。


但是不积跬步无以至千里,如果我们要定制一个好用好看的播放器,还是得先把笨拙的VideoView与MediaController搞清楚才行。就像穷国一开始没有汽车工业,那只能从研究拖拉机开始,没办法一蹴而就强行大跃进呀。


VideoView结合MediaController

VideoView

前面说过,VideoView把SurfaceView与MediaPlayer整合在了一起,所以它不但提供SurfaceView的所有方法,而且提供MediaPlayer的主要方法。如果读者已经用过MediaPlayer/SurfaceView的话,想必对VideoView的常用方法并不陌生,下面是它的常用方法说明:
setVideoPath : 设置视频文件的路径。
setMediaController : 设置播放控制条。
setOnPreparedListener : 设置预备播放监听器。需要重写onPrepared方法,该方法在准备播放时调用。
setOnCompletionListener : 设置结束播放监听器。需要重写onCompletion方法,该方法在结束播放时调用。
setOnErrorListener : 设置播放异常监听器。需要重写onError方法,该方法在播放出现异常时调用。
setOnInfoListener : 设置播放信息监听器。需要重写onInfo方法,该方法在播放需要传递某种消息时调用,如开始/结束缓冲。
requestFocus : 请求获得焦点。该方法在start方法前调用。
start : 开始播放。
pause : 暂停播放。
resume : 恢复播放。
suspend : 结束播放并释放资源。
seekTo : 拖动到指定进度开始播放。
getDuration : 获得视频的总时长。
getCurrentPosition : 获得当前的播放位置。当该方法返回值与getDuration相等时,表示播放到了末尾。
isPlaying : 判断是否在播放。
getBufferPercentage : 获得已缓冲的比例。返回值在0到1之间。


MediaController

VideoView看起来只有光秃秃的视频画面,要想让用户与它进行交互,还得通过MediaController来中转控制操作。MediaController的界面和功能跟Windows平台上的简单播放条几乎一模一样,下面是它的常用方法说明:
setMediaPlayer : 设置播放器。该方法与setAnchorView只能同时调用其中之一。
setAnchorView : 设置绑定的属主视图。该方法与setMediaPlayer只能同时调用其中之一。
show : 显示控制条。
hide : 隐藏控制条。
isShowing : 判断控制条是否显示。
setPrevNextListeners : 设置前一个按钮与后一个按钮的点击监听器。如果没调用该方法,那么前一个按钮与后一个按钮都不会展示。


集成VideoView和MediaController

VideoView继承自SurfaceView,而MediaController继承自FrameLayout,所以理论上这两个控件是可以随意摆放的,但是考虑到用户的使用习惯,它们往往形成一个整体来展示,即MediaController固定位于VideoView的底部。因此我们不会在布局文件中声明MediaController控件,只会声明VideoView控件,然后让控制条附着与视频视图之上。甚至布局文件中都不用声明视频视图,而在代码中动态添加视频画面,由此便衍生出VideoView和MediaController的两种集成方式:
1、在布局文件中声明VideoView。
VideoView对象的使用步骤不变,即先调用setVideoPath方法指定视频文件,然后调用setMediaController方法指定控制条,最后调用start方法开始播放。此时MediaController对象只需调用setMediaPlayer方法指定播放器即可。
2、在代码中动态添加VideoView。
VideoView对象的使用步骤同上。此时MediaController对象的使用步骤发生变化,它不再调用setMediaPlayer方法,改成调用setAnchorView方法,该方法的意思是把MediaController视图添加到属主视图上,如果方法参数是个VideoView对象,则将MediaController视图添加到VideoView对象的上级视图。


两种集成方式在手机屏幕的展示效果基本一样,开发者可根据视频的展示位置来决定采用哪种方式。
下面是VideoView和MediaController的播放效果截图:
Android开发笔记(一百二十五)自定义视频播放器_第1张图片


下面是在布局文件中声明VideoView的代码例子:
import java.util.Map;

import com.aqi00.lib.dialog.FileSelectFragment;
import com.aqi00.lib.dialog.FileSelectFragment.FileSelectCallbacks;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

public class VideoPlayActivity extends Activity implements OnClickListener, FileSelectCallbacks {
	private static final String TAG = "VideoPlayActivity";
	
    private Button btn_open;
	private VideoView vv_play;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_video_play);

        btn_open = (Button) findViewById(R.id.btn_open);
        btn_open.setOnClickListener(this);
        vv_play = (VideoView) findViewById(R.id.vv_play);
    }

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_open) {
			FileSelectFragment.show(this, new String[]{"mp4"}, null);
		}
	}

	@Override
	public void onConfirmSelect(String absolutePath, String fileName, Map<String, Object> map_param) {
		Log.d(TAG, "onConfirmSelect absolutePath=" + absolutePath + ". fileName=" + fileName);
		String file_path = "";
		if (absolutePath != null && fileName != null) {
			file_path = absolutePath + "/" + fileName;
		}
		Toast.makeText(this, "已打开视频", Toast.LENGTH_SHORT).show();
		vv_play.setVideoPath(file_path);
		vv_play.requestFocus();
		
		MediaController mc_play = new MediaController(this);
		vv_play.setMediaController(mc_play);
		mc_play.setMediaPlayer(vv_play);

		vv_play.start();
	}

	@Override
	public boolean isFileValid(String absolutePath, String fileName, Map<String, Object> map_param) {
		return true;
	}
    
}


下面是在代码中动态添加VideoView的代码例子:
import java.util.Map;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

import com.aqi00.lib.dialog.FileSelectFragment;
import com.aqi00.lib.dialog.FileSelectFragment.FileSelectCallbacks;

public class VideoControllerActivity extends Activity implements OnClickListener, FileSelectCallbacks {
	private static final String TAG = "VideoControllerActivity";
	
    private Button btn_open;
    private LinearLayout ll_play;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_video_controller);

        btn_open = (Button) findViewById(R.id.btn_open);
        btn_open.setOnClickListener(this);
        ll_play = (LinearLayout) findViewById(R.id.ll_play);
    }

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_open) {
			FileSelectFragment.show(this, new String[]{"mp4"}, null);
		}
	}

	@Override
	public void onConfirmSelect(String absolutePath, String fileName, Map<String, Object> map_param) {
		Log.d(TAG, "onConfirmSelect absolutePath=" + absolutePath + ". fileName=" + fileName);
		String file_path = "";
		if (absolutePath != null && fileName != null) {
			file_path = absolutePath + "/" + fileName;
		}
		Toast.makeText(this, "已打开视频", Toast.LENGTH_SHORT).show();
		VideoView vv_play = new VideoView(this);
		vv_play.setVideoPath(file_path);
		vv_play.requestFocus();

		MediaController mc_play = new MediaController(this);
		mc_play.setAnchorView(vv_play);
		mc_play.setKeepScreenOn(true);
		
		vv_play.setMediaController(mc_play);
		ll_play.addView(vv_play);
		vv_play.start();
	}

	@Override
	public boolean isFileValid(String absolutePath, String fileName, Map<String, Object> map_param) {
		return true;
	}
    
}


自定义视频播放器

从上面VideoView和MediaController的播放效果来看,这个简单播放器存在若干不足,包括:
1、控制条分上下两行,上面是控制按钮,下面是进度条,高度太宽了;
2、按钮样式无法定制,且不能增加和删除按钮;
3、进度条与播放时间的样式也不能定制;
4、播放器的视频画面不会自动全屏显示;
5、播放器没有实现调大和调小音量;
6、播放器不会自动设置标题和背景;


基于以上情况,我们要想让视频播放器生动活泼起来,势必要自己写一个既好看又好用的播放器。这里既要对VideoView视频视图进行重写,也要对控制条MediaController进行重写。经过进一步的查看源码与深入分析,我们发现播放器的改进主要分为两个方面,一方面是对视频画面做功能方面的增强,另一方面是对控制条做样式方面的定制,所以VideoView和MediaController的改造方案基本确定如下:
1、增强VideoView的功能,可以派生一个子类出来,重写尺寸测量方法onMeasure,实现自动全屏;重写触摸监听方法onTouch,实现音量的调节;以及补充设置标题和背景的新方法;
2、定制MediaController的样式,因为它的内部控件都是私有的,即使继承了也无法修改,因此只能自己写个全新的控制条。好在我们的需求只是更改控制条的样式,没有增加复杂的功能,增添几个指定风格的控件想必大家都很熟练了,唯一的难点在于如何跟VideoVie对象同步当前的播放进度。对于视频画面向控制条通知播放进度,我们可以通过设置定时器来实现;对于控制条向视频画面通知具体操作,我们可以通过点击事件和拖动事件来实现。


如果只是修改代码,其实还不能完全实现自动全屏的功能,主要问题如下:
1、屏幕顶部的系统状态栏依然留在屏幕顶端;
2、App自身的导航栏也仍旧没有隐藏;
3、在视频播放途中,如果手机屏幕发生切换,例如从竖屏变为横屏,那么视频播放就会停止,回到页面刚进去的初始状态;

对于前两个问题,可通过设置页面主题来予以调整,如下所示,设置属性android:windowFullscreen来隐藏系统状态栏,设置属性android:windowNoTitle来去除App的导航栏:
    <style name="FullScreenTheme" parent="AppBaseTheme">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowContentOverlay">@null</item>
    </style>

对于第三个问题,可通过给activity节点设置属性android:configChanges来予以解决。因为默认情况下,App每次切换屏幕都会重启Activity,即先执行原页面的onDestroy方法,再执行新页面的onCreate方法,这便导致还在播放当中的视频被中断返回了。而属性configChanges的意思是屏幕切换时不用重启Activity,只需调用onConfigurationChanged方法来重新设定显示方式,所以给该属性指定若干事件,就可以避免重启Activity的操作了。下面是一个设置的xml例子,其中orientation表示竖屏/横屏切换,keyboardHidden表示键盘弹出/隐藏,screenSize表示屏幕大小发生变化。
        <activity
            android:name=".VideoCustomActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:screenOrientation="sensor"
            android:theme="@style/FullScreenTheme" >
        </activity>


下面是改造之后的视频播放器界面截图:
第一张是播放器启动画面:
Android开发笔记(一百二十五)自定义视频播放器_第2张图片

第二张是播放器播放画面(控制条弹出):
Android开发笔记(一百二十五)自定义视频播放器_第3张图片

第二张是播放器播放画面(控制条隐藏):



下面是自定义视频视图的代码例子:
import com.example.exmvideo.R;
import com.example.exmvideo.util.Utils;
import com.example.exmvideo.util.VolumnManager;

import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation.AnimationListener;
import android.widget.VideoView;

//支持以下功能:自动全屏、调节音量、收缩控制栏、设置背景
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)  //setBackground需要
public class CustomVideoView extends VideoView implements OnTouchListener {

	private Context mContext;
	private AudioManager mAudioManager;
	private VolumnManager mVolumnManager;
	
	private int screenWidth;
	private int screenHeight;
	private int videoWidth;
	private int videoHeight;
	private int realWidth;
	private int realHeight;

	private float mLastMotionX;
	private float mLastMotionY;
	private int startX;
	private int startY;
	private int threshold;
	private boolean isClick = true;
	// 自动隐藏顶部和底部View的时间
	public static final int HIDE_TIME = 5000;

	private View mTopView;
	private View mBottomView;
	private Handler mHandler = new Handler();

	public CustomVideoView(Context context) {
		this(context, null);
	}

	public CustomVideoView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public CustomVideoView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mContext = context;
		mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
		mVolumnManager = new VolumnManager(mContext);
		screenWidth = Utils.getWidthInPx(mContext);
		screenHeight = Utils.getHeightInPx(mContext);
		threshold = Utils.dip2px(mContext, 18);
	}

	private void volumeDown(float delatY) {
		int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
		int current = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
		int down = (int) (delatY / screenHeight * max * 3);
		int volume = Math.max(current - down, 0);
		mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
		int transformatVolume = volume * 100 / max;
		mVolumnManager.show(transformatVolume);
	}

	private void volumeUp(float delatY) {
		int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
		int current = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
		int up = (int) ((delatY / screenHeight) * max * 3);
		int volume = Math.min(current + up, max);
		mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
		int transformatVolume = volume * 100 / max;
		mVolumnManager.show(transformatVolume);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int width = getDefaultSize(realWidth, widthMeasureSpec);
		int height = getDefaultSize(realHeight, heightMeasureSpec);
		if (realWidth > 0 && realHeight > 0) {
			if (realWidth * height > width * realHeight) {
				height = width * realHeight / realWidth;
			} else if (realWidth * height < width * realHeight) {
				width = height * realWidth / realHeight;
			}
		}
		setMeasuredDimension(width, height);
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		final float x = event.getX();
		final float y = event.getY();
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mLastMotionY = y;
			startX = (int) x;
			startY = (int) y;
			break;
		case MotionEvent.ACTION_MOVE:
			float deltaX = x - mLastMotionX;
			float deltaY = y - mLastMotionY;
			float absDeltaX = Math.abs(deltaX);
			float absDeltaY = Math.abs(deltaY);
			// 声音调节标识
			boolean isAdjustAudio = false;
			if (absDeltaX > threshold && absDeltaY > threshold) {
				if (absDeltaX < absDeltaY) {
					isAdjustAudio = true;
				} else {
					isAdjustAudio = false;
				}
			} else if (absDeltaX < threshold && absDeltaY > threshold) {
				isAdjustAudio = true;
			} else if (absDeltaX > threshold && absDeltaY < threshold) {
				isAdjustAudio = false;
			} else {
				return true;
			}
			if (isAdjustAudio) {
				if (deltaY > 0) {
					volumeDown(absDeltaY);
				} else if (deltaY < 0) {
					volumeUp(absDeltaY);
				}
			}
			mLastMotionX = x;
			mLastMotionY = y;
			break;
		case MotionEvent.ACTION_UP:
			if (Math.abs(x - startX) > threshold || Math.abs(y - startY) > threshold) {
				isClick = false;
			}
			mLastMotionX = 0;
			mLastMotionY = 0;
			startX = (int) 0;
			if (isClick) {
				showOrHide();
			}
			isClick = true;
			break;
		default:
			break;
		}
		return true;
	}
	
	public void prepare(View topTiew, View bottomView) {
		mTopView = topTiew;
		mBottomView = bottomView;
		setBackgroundResource(R.drawable.video_bg1);
	}

	public void begin(MediaPlayer mp) {
		setBackground(null);
		if (mp != null) {
			videoWidth = mp.getVideoWidth();
			videoHeight = mp.getVideoHeight();
		}
		realWidth = videoWidth;
		realHeight = videoHeight;
		start();
	}

	public void end(MediaPlayer mp) {
		setBackgroundResource(R.drawable.video_bg3);
		realWidth = screenWidth;
		realHeight = screenHeight;
	}

	public void showOrHide() {
		if (mTopView==null || mBottomView==null) {
			return;
		}
		if (mTopView.getVisibility() == View.VISIBLE) {
			mTopView.clearAnimation();
			Animation animTop = AnimationUtils.loadAnimation(mContext, R.anim.leave_from_top);
			animTop.setAnimationListener(new AnimationImp() {
				@Override
				public void onAnimationEnd(Animation animation) {
					mTopView.setVisibility(View.GONE);
				}
			});
			mTopView.startAnimation(animTop);

			mBottomView.clearAnimation();
			Animation animBottom = AnimationUtils.loadAnimation(mContext, R.anim.leave_from_bottom);
			animBottom.setAnimationListener(new AnimationImp() {
				@Override
				public void onAnimationEnd(Animation animation) {
					mBottomView.setVisibility(View.GONE);
				}
			});
			mBottomView.startAnimation(animBottom);
		} else {
			mTopView.setVisibility(View.VISIBLE);
			mTopView.clearAnimation();
			Animation animTop = AnimationUtils.loadAnimation(mContext, R.anim.entry_from_top);
			mTopView.startAnimation(animTop);

			mBottomView.setVisibility(View.VISIBLE);
			mBottomView.clearAnimation();
			Animation animBottom = AnimationUtils.loadAnimation(mContext, R.anim.entry_from_bottom);
			mBottomView.startAnimation(animBottom);
			mHandler.removeCallbacks(hideRunnable);
			mHandler.postDelayed(hideRunnable, HIDE_TIME);
		}
	}

	private Runnable hideRunnable = new Runnable() {
		@Override
		public void run() {
			showOrHide();
		}
	};

	private class AnimationImp implements AnimationListener {

		@Override
		public void onAnimationEnd(Animation animation) {
		}

		@Override
		public void onAnimationRepeat(Animation animation) {
		}

		@Override
		public void onAnimationStart(Animation animation) {
		}

	}

}


下面是自定义播放控制条的代码例子:
import com.example.exmvideo.R;
import com.example.exmvideo.util.Utils;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class VideoController extends RelativeLayout implements OnClickListener, OnSeekBarChangeListener {
	private static final String TAG = "VideoController";

	private Context mContext;
	private ImageView mImagePlay;
	private TextView mCurrentTime;
	private TextView mTotalTime;
	private SeekBar mSeekBar;
	private int mBeginViewId = 0x7F24FFF0;
	private int dip_10, dip_40;

	private CustomVideoView mVideoView;
	private int mCurrent = 0;
	private int mBuffer = 0;
	private int mDuration = 0;
	private boolean bPause = false;
	
	public VideoController(Context context) {
		this(context, null);
	}

	public VideoController(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		dip_10 = Utils.dip2px(mContext, 10);
		dip_40 = Utils.dip2px(mContext, 40);
		initView();
	}

	private TextView newTextView(Context context, int id) {
		TextView tv = new TextView(context);
		tv.setId(id);
		tv.setGravity(Gravity.CENTER);
		tv.setTextColor(Color.WHITE);
		tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
		params.addRule(RelativeLayout.CENTER_VERTICAL);
		tv.setLayoutParams(params);
		return tv;
	}
	
	private void initView() {
		mImagePlay = new ImageView(mContext);
		RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(dip_40, dip_40);
		imageParams.addRule(RelativeLayout.CENTER_VERTICAL);
		mImagePlay.setLayoutParams(imageParams);
		mImagePlay.setId(mBeginViewId);
		mImagePlay.setOnClickListener(this);
		
		mCurrentTime = newTextView(mContext, mBeginViewId+1);
		RelativeLayout.LayoutParams currentParams = (LayoutParams) mCurrentTime.getLayoutParams();
		currentParams.setMargins(dip_10, 0, 0, 0);
		currentParams.addRule(RelativeLayout.RIGHT_OF, mImagePlay.getId());
		mCurrentTime.setLayoutParams(currentParams);

		mTotalTime = newTextView(mContext, mBeginViewId+2);
		RelativeLayout.LayoutParams totalParams = (LayoutParams) mTotalTime.getLayoutParams();
		totalParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
		mTotalTime.setLayoutParams(totalParams);
		
		mSeekBar = new SeekBar(mContext);
		RelativeLayout.LayoutParams seekParams = new RelativeLayout.LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		totalParams.setMargins(dip_10, 0, dip_10, 0);
		seekParams.addRule(RelativeLayout.CENTER_IN_PARENT);
		seekParams.addRule(RelativeLayout.RIGHT_OF, mCurrentTime.getId());
		seekParams.addRule(RelativeLayout.LEFT_OF, mTotalTime.getId());
		mSeekBar.setLayoutParams(seekParams);
		mSeekBar.setMax(100);
		mSeekBar.setMinimumHeight(100);
		mSeekBar.setThumbOffset(0);
		mSeekBar.setId(mBeginViewId+3);
		mSeekBar.setOnSeekBarChangeListener(this);
	}

	private void reset() {
		if (mCurrent == 0 || bPause) {
			mImagePlay.setImageResource(R.drawable.video_btn_down);
		} else {
			mImagePlay.setImageResource(R.drawable.video_btn_on);
		}
		mCurrentTime.setText(Utils.formatTime(mCurrent));
		mTotalTime.setText(Utils.formatTime(mDuration));
		mSeekBar.setProgress((mCurrent==0)?0:(mCurrent*100/mDuration));
		mSeekBar.setSecondaryProgress(mBuffer);
	}
	
	private void refresh() {
		invalidate();
		requestLayout();
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		removeAllViews();
		reset();
		addView(mImagePlay);
		addView(mCurrentTime);
		addView(mTotalTime);
		addView(mSeekBar);
	}

	@Override
	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
		if (fromUser) {
			int time = progress * mDuration / 100;
			mVideoView.seekTo(time);
		}
	}

	@Override
	public void onStartTrackingTouch(SeekBar seekBar) {
		mSeekListener.onStartSeek();
	}

	@Override
	public void onStopTrackingTouch(SeekBar seekBar) {
		mSeekListener.onStopSeek();
	}
	
	private onSeekChangeListener mSeekListener;
	public static interface onSeekChangeListener {
		public void onStartSeek();
		public void onStopSeek();
	}
	public void setonSeekChangeListener(onSeekChangeListener listener) {
		mSeekListener = listener;
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == mImagePlay.getId()) {
			if (mVideoView.isPlaying()) {
				mVideoView.pause();
				bPause = true;
			} else {
				if (mCurrent == 0) {
					mVideoView.begin(null);
				}
				mVideoView.start();
				bPause = false;
			}
		}
		refresh();
	}
	
	public void setVideoView(CustomVideoView view) {
		mVideoView = view;
		mDuration = mVideoView.getDuration();
	}
	
	public void setCurrentTime(int current_time, int buffer_time) {
		mCurrent = current_time;
		mBuffer = buffer_time;
		refresh();
	}

}


下面是改造之后播放页面的代码例子:
import java.util.Map;

import com.aqi00.lib.dialog.FileSelectFragment;
import com.aqi00.lib.dialog.FileSelectFragment.FileSelectCallbacks;
import com.example.exmvideo.widget.CustomVideoView;
import com.example.exmvideo.widget.VideoController;
import com.example.exmvideo.widget.VideoController.onSeekChangeListener;

import android.app.Activity;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
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.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

public class VideoCustomActivity extends Activity implements 
		OnClickListener, FileSelectCallbacks, onSeekChangeListener {
	private static final String TAG = "VideoCustomActivity";

	private CustomVideoView fsvv_content;
	private TextView tv_open;
	private RelativeLayout rl_top;
	private VideoController mb_play;
	private Handler mHandler = new Handler();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_video_custom);
		fsvv_content = (CustomVideoView) findViewById(R.id.fsvv_content);
		mb_play = (VideoController) findViewById(R.id.mb_play);
		tv_open = (TextView) findViewById(R.id.tv_open);
		rl_top = (RelativeLayout) findViewById(R.id.rl_top);
		
		fsvv_content.prepare(rl_top, mb_play);
		tv_open.setOnClickListener(this);
		mb_play.setonSeekChangeListener(this);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		mHandler.removeCallbacksAndMessages(null);
	}

	private void playVideo(String video_path) {
		fsvv_content.setVideoPath(video_path);
		fsvv_content.requestFocus();
		fsvv_content.setOnPreparedListener(new OnPreparedListener() {
			@Override
			public void onPrepared(MediaPlayer mp) {
				fsvv_content.begin(mp);
				mb_play.setVideoView(fsvv_content);

				mHandler.removeCallbacks(hideRunnable);
				mHandler.postDelayed(hideRunnable, CustomVideoView.HIDE_TIME);
				mHandler.post(refreshRunnable);
			}
		});
		fsvv_content.setOnCompletionListener(new OnCompletionListener() {
			@Override
			public void onCompletion(MediaPlayer mp) {
				fsvv_content.end(mp);
				mb_play.setCurrentTime(0, 0);
			}
		});
		fsvv_content.setOnTouchListener(fsvv_content);
	}

	private Runnable hideRunnable = new Runnable() {
		@Override
		public void run() {
			fsvv_content.showOrHide();
		}
	};

	private Runnable refreshRunnable = new Runnable() {
		@Override
		public void run() {
			if (fsvv_content.isPlaying()) {
				mb_play.setCurrentTime(fsvv_content.getCurrentPosition(), fsvv_content.getBufferPercentage());
			}
			mHandler.postDelayed(this, 500);
		}
	};

	@Override
	public void onClick(View v) {
		int resid = v.getId();
		if (resid == R.id.tv_open) {
			FileSelectFragment.show(this, new String[]{"mp4"}, null);
		}
	}

	@Override
	public void onConfirmSelect(String absolutePath, String fileName, Map<String, Object> map_param) {
		Log.d(TAG, "onConfirmSelect absolutePath=" + absolutePath + ". fileName=" + fileName);
		String file_path = "";
		if (absolutePath != null && fileName != null) {
			file_path = absolutePath + "/" + fileName;
		}
		Toast.makeText(this, "已打开视频", Toast.LENGTH_SHORT).show();
		playVideo(file_path);
	}

	@Override
	public boolean isFileValid(String absolutePath, String fileName, Map<String, Object> map_param) {
		return true;
	}

	@Override
	public void onStartSeek() {
		mHandler.removeCallbacks(hideRunnable);
	}

	@Override
	public void onStopSeek() {
		mHandler.postDelayed(hideRunnable, CustomVideoView.HIDE_TIME);
	}

}



点击下载本文用到的自定义视频播放器的工程代码



点此查看Android开发笔记的完整目录

你可能感兴趣的:(android,视频播放,mediaplayer,VideoView,mediaController)