前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(一百二十五)自定义视频播放器

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

作者头像
aqi00
发布2019-01-18 14:59:32
2.6K0
发布2019-01-18 14:59:32
举报
文章被收录于专栏:老欧说安卓老欧说安卓

视频播放方式

在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的播放效果截图:

下面是在布局文件中声明VideoView的代码例子:

代码语言:javascript
复制
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的代码例子:

代码语言:javascript
复制
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的导航栏:

代码语言:javascript
复制
    <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表示屏幕大小发生变化。

代码语言:javascript
复制
        <activity
            android:name=".VideoCustomActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:screenOrientation="sensor"
            android:theme="@style/FullScreenTheme" >
        </activity>

下面是改造之后的视频播放器界面截图: 第一张是播放器启动画面:

第二张是播放器播放画面(控制条弹出):

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

下面是自定义视频视图的代码例子:

代码语言:javascript
复制
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) {
		}

	}

}

下面是自定义播放控制条的代码例子:

代码语言:javascript
复制
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();
	}

}

下面是改造之后播放页面的代码例子:

代码语言:javascript
复制
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开发笔记的完整目录

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年09月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 视频播放方式
  • VideoView结合MediaController
    • VideoView
      • MediaController
        • 集成VideoView和MediaController
        • 自定义视频播放器
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档