2010.03.27 SeeJoPlayer v1.2.0 beta版:
更新说明:
1、完美支持android1.5、android1.6、android2.0、android2.01、android2.1平台;
2、完美支持320×480、480×800、480×854等各种分辨率(自适应屏幕分辨率);
3、支持在线音视频播放,支持URL input和从浏览器调用SeeJoPlayer播放器播放在线音视频;
4、自动转为横屏播放,为用户提供更好的观看体验;
5、修改了没有SD卡程序出错的Bug;
6、美化了视频播放列
和操作说明的界面。
第一部分:功能介绍
SeeJoPlayer的优点主要在相对还算美观的界面和便捷的交互操作上。先说操作吧,它支持:
1、全屏切换: 双击屏幕
2、播放/暂停: 长按屏幕
3、静音/恢复: 长按音量按钮
4、播放列表: 控制面板最右边的按钮(暂不支持编辑功能)
5、音量调节: 单击音量按钮,在弹出的音量显示区域触摸改变音量
这些操作和PC上的播放器较为类似,希望大家能用得习惯。
至于界面的话,多说无益,直接上图吧:
第二部分:源码解析
一、VideoView与视频比例缩放:
我们可以很方便的获得VideoView的源代码,最简单的
是直接在GoogleCodeSearch上找“VideoView.java”。所以重写VideoView的过程其实只是在原来的基础上进行一些修改而已,并非一个很麻烦的工作。为什么Android自带的VideoView会保持视频的长宽比而不能让我们很方便的自定义比例呢?我猜想可能Google做Android也是一个很仓促的工程,许多代码并没有考虑得太成熟。
VideoView的源码中有这样一段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
protected
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Log.i("@@@@", "onMeasure");
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth >0&& mVideoHeight >0) {
if ( mVideoWidth * height > width * mVideoHeight ) {
//Log.i("@@@", "image too tall, correcting");
height = width * mVideoHeight / mVideoWidth;
}
else
if ( mVideoWidth * height < width * mVideoHeight ) {
//Log.i("@@@", "image too wide, correcting");
width = height * mVideoWidth / mVideoHeight;
}
else
{
//Log.i("@@@", "aspect ratio is correct: " +
//width+"/"+height+"="+
//mVideoWidth+"/"+mVideoHeight);
}
}
//Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
setMeasuredDimension(width, height);
}
这就是为什么长宽比不能改变的原因了。因为在OnMeasure的时候,就对这个长宽比进行了处理。
我们把其中处理的代码屏蔽掉,视频大小就可以随着VideoView的长宽改变而改变了。
1
2
3
4
5
6
7
8
9
10
11
12
protected
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Log.i("@@@@", "onMeasure");
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
setMeasuredDimension(width,height);
}
二、视频控制菜单与播放界面的层次问题:
看到过一些别人写的视频播放器,其中有一些朋友老是简简单单的将VideoView和控制界面放在一个LinearLayout中。这样随着控制界面的出 现与否,VideoView会随之改变长宽,给人的体验并不很好。所以,我认为VideoView和控制界面最好不要放在同一个层次上。不要偷懒,使用一 个FrameLayout或者PopupWindow就可以解决这个问题。例如,我就简简单单地使用了PopupWindow,这个具体实现上,就百花争 鸣吧。
三、视频文件扫描:
视频文件的扫描,现在想来主要有两种方式:
第一种就是直接读取媒体库中的视频文件数据库。当Android启动的时候,系统会自动扫描sdcard,并为媒体文件建立(或者更新)数据库。我们可以通过对应的URI来访问数据库,从而得到视频文件的列表:
1
2
3
4
5
6
7
8
9
10
11
12
private Uri videoListUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = getContentResolver().query(videoListUri, new String[]{"_display_name","_data"}, null, null, null);
int n = cursor.getCount();
cursor.moveToFirst();
LinkedList
playList2 = new LinkedList();
for(int i = 0 ; i != n ; ++i){
MovieInfo mInfo = new MovieInfo();
mInfo.displayName = cursor.getString(cursor.getColumnIndex("_display_name"));
mInfo.path = cursor.getString(cursor.getColumnIndex("_data"));
playList2.add(mInfo);
cursor.moveToNext();
}
这种方法可能是最有效率的了,不过不知为何,媒体库中似乎没有扫描进本身支持的3GP视频格式(也可能我这里是一个特例) 。不过,正是因为这个原因,我才想到有可能需要另外一种最基本的扫描文件系统的方法来扫描视频文件。这就是文件系统的遍历:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void getVideoFile(final LinkedList list,File file){
file.listFiles(new FileFilter(){
@Override
public boolean accept(File file) {
// TODO Auto-generated method stub
String name = file.getName();
int i = name.indexOf('.');
if(i != -1){
name = name.substring(i);
if(name.equalsIgnoreCase(".mp4")||name.equalsIgnoreCase(".3gp")){
MovieInfo mi = new MovieInfo();
mi.displayName = file.getName();
mi.path = file.getAbsolutePath();
list.add(mi);
return true;
}
}else if(file.isDirectory()){
getVideoFile(list, file);
}
return false;
}
});
}
当然,随着Android平台下的硬件设备越来越多,越来越强大。我们有理由相信,它以后将不仅仅只支持MP4和3GP格式的视频文件,所以我们必须使用两种方式结合的方法来获得最大的视频集合作为我们的视频列表。
四、播放过程中进度条progress的设定:
视频开始播放了,那么一个小麻烦出现了:什么时候设定进度条才更有效率?我这里有一种方法供大家参考,那就是通过Handler自己给自己发消息来达到不断设置进度条的目的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Handler myHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
switch(msg.what){
case PROGRESS_CHANGED:
int i = vv.getCurrentPosition();
seekBar.setProgress(i);
i/=1000;
int minute = i/60;
int hour = minute/60;
int second = i%60;
minute %= 60;
playedTextView.setText(String.format("%02d:%02d:%02d", hour,minute,second));
sendEmptyMessage(PROGRESS_CHANGED);
break;
当然,这种方法,需要首先发送一个初始消息来启动。
五、全屏与非全屏:
大家都知道,一般一个Activity设置全屏的方法有两种,一是在OnCreate中:
1
2
3
4
5
6
7
8
9
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
Window win = getWindow();
win.setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,
WindowManager.LayoutParams.NO_STATUS_BAR_FLAG);
setContentView(R.layout.mylayout);
二是在AndroidManifest.xml中:
1
2
3
然而,这两种方法都不能达到我们在视频播放过程中设置全屏与否的目的。因为它们都只能在初始化的时候决定全屏与否。那么我现在要说的就是第三种方法:
1getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
1getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
这种方法就可以在Activity运行过程中,动态地改变全屏与否。
六、音量调节:
音量调节的方法其实很简单,不过有人问到,我就在这里顺便说下:
1 AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
2 setIndex(am.getStreamVolume(AudioManager.STREAM_MUSIC));