视频播放方式
在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的代码例子:
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>
下面是改造之后的视频播放器界面截图:
第一张是播放器启动画面:
第二张是播放器播放画面(控制条弹出):
第二张是播放器播放画面(控制条隐藏):
下面是自定义视频视图的代码例子:
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开发笔记的完整目录