转载注明出处:
http://blog.csdn.net/u010181592/article/category/5893483
文章出自 我不只是看客/NotLooker的博客
先列出参考资料:
- Vitamio 官网:http://www.vitamio.org(不太稳定,时常打不开)
- 农民伯伯 博客:http://www.cnblogs.com/over140/category/409230.html(开发者之一,博客中有部分Vitamio中文API)
转载注明出处:http://blog.csdn.net/u010181592/article/category/5893483
经过准备和第一个Demo之后,需要对视频进行更加精细的DIY来适合自己的项目。首先,视频播放界面给大家直观的感受就是 有进度条,声音,按钮等的控制条了,Vitamio中提供了一个基础的样例,我们根据自己的需要进行修改;
先来看一下Vitamio自带的 MediaController 因为篇幅的缘故,就不上源码。只列出介绍和常用的函数:
/**
* A view containing controls for a MediaPlayer. Typically contains the buttons
* like “Play/Pause” and a progress slider. It takes care of synchronizing the
* controls with the state of the MediaPlayer.
*
* The way to use this class is to a) instantiate it programatically or b)
* create it in your xml layout.
*
* a) The MediaController will create a default set of controls and put them in
* a window floating above your application. Specifically, the controls will
* float above the view specified with setAnchorView(). By default, the window
* will disappear if left idle for three seconds and reappear when the user
* touches the anchor view. To customize the MediaController’s style, layout and
* controls you should extend MediaController and override the {#link
* {@link #makeControllerView()} method.
*
* b) The MediaController is a FrameLayout, you can put it in your layout xml
* and get it through {@link #findViewById(int)}.
*
* NOTES: In each way, if you want customize the MediaController, the SeekBar’s
* id must be mediacontroller_progress, the Play/Pause’s must be
* mediacontroller_pause, current time’s must be mediacontroller_time_current,
* total time’s must be mediacontroller_time_total, file name’s must be
* mediacontroller_file_name. And your resources must have a pause_button
* drawable and a play_button drawable.
*
* Functions like show() and hide() have no effect when MediaController is
* created in an xml layout.
*/
博主的英文不是太爱好,看介绍也就能理解的意思大概是 控制器是一个包含了MediaPlayer(视频播放核心组件)的视图控件,会有常用的按钮和功能。使用方法有2种:
a)创建一个xml布局,MediaController将会把他们放到 漂浮在应用上的窗口(看代码后发现就是PopWindow) 默认显示时间3s,点击视图可控制显示/消失 要自定义MediaController时需要继承该类并覆盖makeControllerView()方法
b)直接把它放到你的布局文件中
<com.test.myapplication.MyMediaController
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.test.myapplication.MyMediaController>
无论使用哪种方法, SeekBar的ID必须是mediacontroller_progress
播放/暂停的必须是 mediacontroller_pause
显示当前时间 mediacontroller_time_current
显示总时长 mediacontroller_time_total
播放文件名 mediacontroller_file_name
而且必须有 pause_button和play_button 的资源(个人没能理解这条,很多播放器设计已经用点击屏幕代替掉了开始暂停实体键,博主测试的时候发现,即使自己的布局没有开始按钮,通过双击仍然可以暂停开始)
如果你使用第二种方法 那么hide()和show()无效;
下边是常用方法的中文API,引用自农民伯伯的博客
public void onFinishInflate()
从XML加载完所有子视图后调用。初始化控制视图(调用initControllerView方法,设置事件、绑定控件和设置默认值)。public void setAnchorView(View view)
设置MediaController绑定到一个视图上。例如可以是一个VideoView对象,或者是你的activity的主视图。
参数 view 可见时绑定的视图public void setMediaPlayer(MediaPlayerControl player)
设置媒体播放器。并更新播放/暂停按钮状态。public void setInstantSeeking(boolean seekWhenDragging)
设置用户拖拽SeekBar时画面是否跟着变化。(VPlayer默认完成操作后再更新画面)public void show()
显示MediaController。默认显示3秒后自动隐藏。public void show(int timeout)
显示MediaController。在timeout毫秒后自动隐藏。
参数 timeout 超时时间,单位毫秒。为0时控制条的hide()将被调用。public void setFileName(String name)
设置视频文件名称。public void setInfoView(OutlineTextView v)
设置保存MediaController的操作信息。例如进度改变时更新v。public void setAnimationStyle(int animationStyle)
更改MediaController的动画风格。
如果MediaController正在显示,调用此方法将在下一次显示时生效。
参数 animationStyle 在MediaController显示或隐藏时使用的动画风格。设置-1为默认风格,0没有动画,或设置一个明确的动画资源。public boolean isShowing()
获取MediaController是否已经显示。public void hide()
隐藏MediaController。public void setOnShownListener(OnShownListener l)
注册一个回调函数,在MediaController显示后被调用。public void setOnHiddenListener(OnHiddenListener l)
注册一个回调函数,在MediaController隐藏后被调用。public boolean onTouchEvent(MotionEvent event)
调用show()并返回true。public boolean onTrackballEvent(MotionEvent ev)
调用show()并返回false。public void setEnabled(boolean enabled)
设置MediaController的可用状态。包括进度条和播放/暂停按钮。受保护方法
protected View makeControllerView()
创建控制播放的布局视图。子类可重写此方法创建自定义视图。
例如我们要实现如下一个视频播放器:
它有一个自定义的标题栏,并且把文件名放到了视频的顶端;
在标题栏的左边有一个返回按钮,右边分别由电池电量显示和时间信息;
很多人开始做的时候会做这种布局会把标题栏写在播放界面VideoView的布局上,就像这样:
这样标题栏和控制器的逻辑就是分开的,有2个弊端 :
- 1 在监听手指点击屏幕相应事件中你不能完美的使 标题栏和控制栏同时出现/消失,在点击时总要判断控制栏的show/hide
- 2 当你需要在控制器增加功能性按钮(如清晰度设置,剧集选择时,你就要重写控制器或者直接去修改库中的源代码,总感觉第二种行为不太好)
既然以后对控制器的要求会更高不如从现在开始就直接自己定义,方便以后改写; so ~ 重写开始:
首先看一下布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="34dp"
android:background="#77000000">
<ImageButton
android:id="@+id/mediacontroller_top_back"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:background="@null"
android:src="@drawable/btn_nav_back_n"/>
<TextView
android:id="@+id/mediacontroller_file_name"
style="@style/MediaController_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@+id/mediacontroller_top_back"
android:ellipsize="marquee"
android:singleLine="true"
android:text="file name"/>
<TextView
android:id="@+id/mediacontroller_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:text="17:22"
android:textColor="#ffffff"
android:textSize="15sp"/>
<ImageView
android:id="@+id/mediacontroller_imgBattery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/mediacontroller_time"
android:gravity="center_vertical"
android:src="@drawable/battery"/>
<TextView
android:id="@+id/mediacontroller_Battery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="-10dp"
android:layout_toLeftOf="@+id/mediacontroller_imgBattery"
android:gravity="center_vertical"
android:text="45%"
android:textColor="#ffffff"
android:textSize="15sp"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/rl_med"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:background="#77000000">
<ImageButton
android:id="@+id/mediacontroller_play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:background="@drawable/mediacontroller_button"
android:contentDescription="@string/mediacontroller_play_pause"
android:src="@drawable/mediacontroller_pause"/>
<TextView
android:id="@+id/mediacontroller_time_current"
style="@style/MediaController_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@id/mediacontroller_play_pause"
android:text="33:33:33"
/>
<TextView
android:id="@+id/mediacontroller_time_total"
style="@style/MediaController_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:text="33:33:33"/>
<SeekBar
android:id="@+id/mediacontroller_seekbar"
style="@style/MediaController_SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/mediacontroller_time_total"
android:layout_toRightOf="@id/mediacontroller_time_current"
android:focusable="true"
android:max="1000"/>
</RelativeLayout>
</RelativeLayout>
`
把需要显示的标题栏 和控制器整合到了一起,这样可以完美配合;
下面是自己的控制器类 MyMediaController:
public class MyMediaController extends MediaController {
private GestureDetector mGestureDetector;
private ImageButton img_back;//返回键
private ImageView img_Battery;//电池电量显示
private TextView textViewTime;//时间提示
private TextView textViewBattery;//文字显示电池
private VideoView videoView;
private Activity activity;
private Context context;
private int controllerWidth = 0;//设置mediaController高度为了使横屏时top显示在屏幕顶端
//返回监听
private View.OnClickListener backListener = new View.OnClickListener() {
public void onClick(View v) {
if(activity != null){
activity.finish();
}
}
};
//videoview 用于对视频进行控制的等,activity为了退出
public MyMediaController(Context context, VideoView videoView , Activity activity) {
super(context);
this.context = context;
this.videoView = videoView;
this.activity = activity;
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
controllerWidth = wm.getDefaultDisplay().getWidth();
mGestureDetector = new GestureDetector(context, new MyGestureListener());
}
@Override
protected View makeControllerView() {
View v = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("mymediacontroller", "layout", getContext().getPackageName()), this);
v.setMinimumHeight(controllerWidth);
img_back = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_top_back", "id", context.getPackageName()));
img_Battery = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_imgBattery", "id", context.getPackageName()));
img_back.setOnClickListener(backListener);
textViewBattery = (TextView)v.findViewById(getResources().getIdentifier("mediacontroller_Battery", "id", context.getPackageName()));
textViewTime = (TextView)v.findViewById(getResources().getIdentifier("mediacontroller_time", "id", context.getPackageName()));
return v;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
System.out.println("MYApp-MyMediaController-dispatchKeyEvent");
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event)) return true;
// 处理手势结束
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//当收拾结束,并且是单击结束时,控制器隐藏/显示
toggleMediaControlsVisiblity();
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return super.onScroll(e1, e2, distanceX, distanceY);
}
//双击暂停或开始
@Override
public boolean onDoubleTap(MotionEvent e) {
playOrPause();
return true;
}
}
public void setTime(String time){
if (textViewTime != null)
textViewTime.setText(time);
}
//显示电量,
public void setBattery(String stringBattery){
if(textViewTime != null && img_Battery != null){
textViewBattery.setText( stringBattery + "%");
int battery = Integer.valueOf(stringBattery);
if(battery < 15)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_15));
if(battery < 30 && battery >= 15)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_15));
if(battery < 45 && battery >= 30)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_30));
if(battery < 60 && battery >= 45)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_45));
if(battery < 75 && battery >= 60)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_60));
if(battery < 90 && battery >= 75)img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_75));
if(battery > 90 )img_Battery.setImageDrawable(getResources().getDrawable(R.drawable.battery_90));
}
}
//隐藏/显示
private void toggleMediaControlsVisiblity(){
if (isShowing()) {
hide();
} else {
show();
}
}
//播放与暂停
private void playOrPause(){
if (videoView != null)
if (videoView.isPlaying()) {
videoView.pause();
} else {
videoView.start();
}
}
}
`
因为使用的是自定义的mediaController 当显示后,mediaController会铺满屏幕,所以VideoView的点击事件会被拦截,所以重写控制器的手势事件,将全部的操作全部写在控制器中,
另外,因为点击事件被控制器拦截,无法传递到下层的VideoView,所以 原来的单机隐藏会失效,作为代替,在手势监听中onSingleTapConfirmed()添加自定义的隐藏/显示,
下边是Activity代码
public class PlayActivity extends Activity implements Runnable{
private VideoView mVideoView;
private MediaController mMediaController;
private MyMediaController myMediaController;
String path1 = Environment.getExternalStorageDirectory() + "/Download/B.mp4";
private static final int TIME = 0;
private static final int BATTERY = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TIME:
myMediaController.setTime(msg.obj.toString());
break;
case BATTERY:
myMediaController.setBattery(msg.obj.toString());
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//定义全屏参数
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
//获得当前窗体对象
Window window = PlayActivity.this.getWindow();
//设置当前窗体为全屏显示
window.setFlags(flag, flag);
//设置视频解码监听
if (!io.vov.vitamio.LibsChecker.checkVitamioLibs(this))
return;
setContentView(R.layout.activity_play);
mVideoView = (VideoView) findViewById(R.id.surface_view);
mVideoView.setVideoPath(path1);
mMediaController = new MediaController(this);
myMediaController = new MyMediaController(this,mVideoView,this);
mVideoView.setMediaController(myMediaController);
//mVideoView.setMediaController(mMediaController);
mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//高画质
mMediaController.show(5000);
mVideoView.requestFocus();
registerBoradcastReceiver();
new Thread(this).start();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mVideoView != null){
mVideoView.setVideoLayout(VideoView.VIDEO_LAYOUT_SCALE, 0);
}
super.onConfigurationChanged(newConfig);
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
unregisterReceiver(batteryBroadcastReceiver);
} catch (IllegalArgumentException ex) {
}
}
private BroadcastReceiver batteryBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())){
//获取当前电量
int level = intent.getIntExtra("level", 0);
//电量的总刻度
int scale = intent.getIntExtra("scale", 100);
//把它转成百分比
//tv.setText("电池电量为"+((level*100)/scale)+"%");
Message msg = new Message();
msg.obj = (level*100)/scale+"";
msg.what = BATTERY;
mHandler.sendMessage(msg);
}
}
};
public void registerBoradcastReceiver() {
//注册电量广播监听
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(batteryBroadcastReceiver, intentFilter);
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//时间读取线程
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
String str = sdf.format(new Date());
Message msg = new Message();
msg.obj = str;
msg.what = TIME;
mHandler.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
因为要使视频全屏才能达到最佳效果。直接在Activity中设置了全屏,为了达到只能横向旋转切换效果,在AndroidMainfest.xml中对Activity的属性这么设置
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="sensorLandscape"
在4.0 以后的系统中 原来的android:configChanges=orientation|keyboardHidden 将不会起作用,需要增加screenSize才可以正常的调用重绘函数
android:screenOrientation 属性相关的值以及功能如下:
screenOrientation | Function |
---|---|
unspecified | 默认值,由系统来选择方向。它的使用策略,以及由于选择时特定的上下文环境,可能会因 为设备的差异而不同。 |
user | 使用用户当前首选的方向。 |
behind | 使用Activity堆栈中与该Activity之下的那个Activity的相同的方向。 |
landscape | 横向显示(宽度比高度要大) |
portrait | 纵向显示(高度比宽度要大) |
reverseLandscape | 与正常的横向方向相反显示,在API Level 9中被引入。 |
reversePortrait | 与正常的纵向方向相反显示,在API Level 9中被引入。 |
sensorLandscape | 横向显示,但是基于设备传感器,既可以是按正常方向显示,也可以反向显示,在API Level 9中被引入。 |
sensorPortrait | 纵向显示,但是基于设备传感器,既可以是按正常方向显示,也可以反向显示,在API Level 9中被引入。 |
sensor | 显示的方向是由设备的方向传感器来决定的。显示方向依赖与用户怎样持有设备;当用户旋转设备时,显示的方向会改变。但是,默认情况下,有些设备不会在所有的四个方向上都旋转,因此要允许在所有的四个方向上都能旋转,就要使用fullSensor属性值。 |
fullSensor | 显示的方向(4个方向)是由设备的方向传感器来决定的,除了它允许屏幕有4个显示方向之外,其他与设置为“sensor”时情况类似,不管什么样的设备,通常都会这么做。例如,某些设备通常不使用纵向倒转或横向反转,但是使用这个设置,还是会发生这样的反转。这个值在API Level 9中引入。 |
nosensor | 屏幕的显示方向不会参照物理方向传感器。传感器会被忽略,所以显示不会因用户移动设备而旋转。除了这个差别之外,系统会使用与“unspecified”设置相同的策略来旋转屏幕的方向。 |
注意:在给这个属性设置的值是“landscape”或portrait的时候,要考虑硬件对Activity运行的方向要求。正因如此,这些声明的值能够被诸如Google Play这样的服务所过滤,以便应用程序只能适用于那些支持Activity所要求的方向的设备。例如,如果声明了“landscape”、“reverseLandscape”、或“sensorLandscape”,那么应用程序就只能适用于那些支持横向显示的设备。但是,还应该使用元素来明确的声明应用程序所有的屏幕方向是纵向的还是横行的。例如:。这个设置由Google Play提供的纯粹的过滤行为,并且在设备仅支持某个特定的方向时,平台本身并不控制应用程序是否能够被按照。
这样一个自定义的控制栏和标题栏就完成了
Github Demo:WHPlayer
结束,下一篇将讲述 如何添加,滑动调节亮度,声音,快进快退等