SeeJoPlayer是我利用业余时间开发的一款免费的视频播放器。主要是现在在网上似乎找不到一个Android平台下的界面美观一点的视频播放器。而作为智能手机操作系统的Android,没有一个像样一点的视频播放器,岂不糗大了。所以,我就写了这么一个砖头并开出源码,希望能引出高手们的美玉来吧!
第二部分:源码解析
SeeJoPlayer不是一个完美的作品,可以说,它在很多地方都不尽如人意。当然一个完美的作品,也不是我写这款播放器的目的。我只是希望以此为引,结合大家共同的智慧开发出一款真正完美强大的Android平台下的国产视频播放器出来。
SeeJoPlayer有许多不足之处,例如,它只支持系统默认的视频格式,因为它使用系统默认的解码器。这,一方面是因为如果通过软解码的话,播放视频的效率会很受影响,另外最主要的原因当然还是个人水平、精力有限,没办法接着往下做了。如果大家觉得这份代码还多少有些参考价值的话,不妨拿去用。只是希望当你们以此为参考,开发出真正强大的播放器出来的时候,别忘了如果能开放源码的话,一定开放出来。毕竟开源软件就好比能够进化的物种,提供你的DNA出来,让我们共同的软件变得越来越完美吧!
好了,废话不说了。播放器的全部源码本文中已经提供了下载地址。下面,我就其中我觉得可能值得关注的地方做一些解释。
一、VideoView与视频比例缩放:
以前在论坛上也看到有人问过如何实现视频按比例缩放的问题。的确,如果仅仅使用VideoView可能达不到我们想要达到的效果。这就需要我们对VideoView做一些改动,简单的说就是另外写一个类似VideoView的类出来(庆幸Android是开源的)。
我们可以很方便的获得VideoView的源代码,最简单的方法是直接在 GoogleCodeSearch上找“VideoView.java”。所以重写VideoView的过程其实只是在原来的基础上进行一些修改而已,并非一个很麻烦的工作。为什么Android自带的VideoView会保持视频的长宽比而不能让我们很方便的自定义比例呢?我猜想可能Google做Android也是一个很仓促的工程,许多代码并没有考虑得太成熟。
VideoView的源码中有这样一段代码:
1 @Override
2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 //Log.i("@@@@", "onMeasure");
4 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
5 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
6 if (mVideoWidth > 0 && mVideoHeight > 0) {
7 if ( mVideoWidth * height > width * mVideoHeight ) {
8 //Log.i("@@@", "image too tall, correcting");
9 height = width * mVideoHeight / mVideoWidth;
10 } else if ( mVideoWidth * height < width * mVideoHeight ) {
11 //Log.i("@@@", "image too wide, correcting");
12 width = height * mVideoWidth / mVideoHeight;
13 } else {
14 //Log.i("@@@", "aspect ratio is correct: " +
15 //width+"/"+height+"="+
16 //mVideoWidth+"/"+mVideoHeight);
17 }
18 }
19 //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
20 setMeasuredDimension(width, height);
21 }
22
这就是为什么长宽比不能改变的原因了。因为在OnMeasure的时候,就对这个长宽比进行了处理。
我们把其中处理的代码屏蔽掉,视频大小就可以随着VideoView的长宽改变而改变了。
1 @Override
2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 //Log.i("@@@@", "onMeasure");
4 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
5 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
6 /**//*if (mVideoWidth > 0 && mVideoHeight > 0) {
7 if ( mVideoWidth * height > width * mVideoHeight ) {
8 //Log.i("@@@", "image too tall, correcting");
9 height = width * mVideoHeight / mVideoWidth;
10 } else if ( mVideoWidth * height < width * mVideoHeight ) {
11 //Log.i("@@@", "image too wide, correcting");
12 width = height * mVideoWidth / mVideoHeight;
13 } else {
14 //Log.i("@@@", "aspect ratio is correct: " +
15 //width+"/"+height+"="+
16 //mVideoWidth+"/"+mVideoHeight);
17 }
18 }*/
19 //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
20 setMeasuredDimension(width,height);
21 }
二、视频控制菜单与播放界面的层次问题:
看到过一些别人写的视频播放器,其中有一些朋友老是简简单单的将VideoView和控制界面放在一个LinearLayout中。这样随着控制界面的出现与否,VideoView会随之改变长宽,给人的体验并不很好。所以,我认为VideoView和控制界面最好不要放在同一个层次上。不要偷懒,使用一个FrameLayout或者PopupWindow就可以解决这个问题。例如,我就简简单单地使用了PopupWindow,这个具体实现上,就百花争鸣吧。
三、视频文件扫描:
视频文件的扫描,现在想来主要有两种方式:
第一种就是直接读取媒体库中的视频文件数据库。当Android启动的时候,系统会自动扫描sdcard,并为媒体文件建立(或者更新)数据库。我们可以通过对应的URI来访问数据库,从而得到视频文件的列表:
1 private Uri videoListUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
2
3
4
5 Cursor cursor = getContentResolver().query(videoListUri, new String[]{"_display_name","_data"}, null, null, null);
6 int n = cursor.getCount();
7 cursor.moveToFirst();
8 LinkedList<MovieInfo> playList2 = new LinkedList<MovieInfo>();
9 for(int i = 0 ; i != n ; ++i){
10 MovieInfo mInfo = new MovieInfo();
11 mInfo.displayName = cursor.getString(cursor.getColumnIndex("_display_name"));
12 mInfo.path = cursor.getString(cursor.getColumnIndex("_data"));
13 playList2.add(mInfo);
14 cursor.moveToNext();
15 }
这种方法可能是最有效率的了,不过不知为何,媒体库中似乎没有扫描进本身支持的3GP视频格式(也可能我这里是一个特例) 。不过,正是因为这个原因,我才想到有可能需要另外一种最基本的扫描文件系统的方法来扫描视频文件。这就是文件系统的遍历:
1 private void getVideoFile(final LinkedList<MovieInfo> list,File file){
2
3 file.listFiles(new FileFilter(){
4
5 @Override
6 public boolean accept(File file) {
7 // TODO Auto-generated method stub
8 String name = file.getName();
9 int i = name.indexOf('.');
10 if(i != -1){
11 name = name.substring(i);
12 if(name.equalsIgnoreCase(".mp4")||name.equalsIgnoreCase(".3gp")){
13
14 MovieInfo mi = new MovieInfo();
15 mi.displayName = file.getName();
16 mi.path = file.getAbsolutePath();
17 list.add(mi);
18 return true;
19 }
20 }else if(file.isDirectory()){
21 getVideoFile(list, file);
22 }
23 return false;
24 }
25 });
26 }
当然,随着Android平台下的硬件设备越来越多,越来越强大。我们有理由相信,它以后将不仅仅只支持MP4和3GP格式的视频文件,所以我们必须使用两种方式结合的方法来获得最大的视频集合作为我们的视频列表。
四、播放过程中进度条progress的设定:
视频开始播放了,那么一个小麻烦出现了:什么时候设定进度条才更有效率?我这里有一种方法供大家参考,那就是通过Handler自己给自己发消息来达到不断设置进度条的目的。
1 Handler myHandler = new Handler(){
2
3 @Override
4 public void handleMessage(Message msg) {
5 // TODO Auto-generated method stub
6
7 switch(msg.what){
8
9 case PROGRESS_CHANGED:
10
11 int i = vv.getCurrentPosition();
12 seekBar.setProgress(i);
13
14 i/=1000;
15 int minute = i/60;
16 int hour = minute/60;
17 int second = i%60;
18 minute %= 60;
19 playedTextView.setText(String.format("%02d:%02d:%02d", hour,minute,second));
20
21 sendEmptyMessage(PROGRESS_CHANGED);
22 break;
23
当然,这种方法,需要首先发送一个初始消息来启动。
五、全屏与非全屏:
大家都知道,一般一个Activity设置全屏的方法有两种,一是在OnCreate中:
1 @Override
2 public void onCreate(Bundle icicle) {
3 super.onCreate(icicle);
4
5 requestWindowFeature(Window.FEATURE_NO_TITLE);
6 Window win = getWindow();
7 win.setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,
8 WindowManager.LayoutParams.NO_STATUS_BAR_FLAG);
9
10 setContentView(R.layout.mylayout);
11
12
13
二是在AndroidManifest.xml中:
1<activity android:name=".MyActivity"
2 android:label=""
3 android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
然而,这两种方法都不能达到我们在视频播放过程中设置全屏与否的目的。因为它们都只能在初始化的时候决定全屏与否。那么我现在要说的就是第三种方法:
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));
好了,就写这些了吧。可能这些知识有人知道,或者还有些盲点我没有讲到。欢迎大家与我联系,大家一起多多讨论交流,并且整个源码都开放出来了,大家一定可以把来龙去脉弄得一清二楚的!最后,多谢大家听我罗嗦,欢迎使用SeeJoPlayer,欢迎阅读其源码!本文也欢迎大家转载,不过转载请注明出处: http://www.blogjava.net/zh-weir/archive/2010/01/24/310617.html
第二部分:源码解析
SeeJoPlayer不是一个完美的作品,可以说,它在很多地方都不尽如人意。当然一个完美的作品,也不是我写这款播放器的目的。我只是希望以此为引,结合大家共同的智慧开发出一款真正完美强大的Android平台下的国产视频播放器出来。
SeeJoPlayer有许多不足之处,例如,它只支持系统默认的视频格式,因为它使用系统默认的解码器。这,一方面是因为如果通过软解码的话,播放视频的效率会很受影响,另外最主要的原因当然还是个人水平、精力有限,没办法接着往下做了。如果大家觉得这份代码还多少有些参考价值的话,不妨拿去用。只是希望当你们以此为参考,开发出真正强大的播放器出来的时候,别忘了如果能开放源码的话,一定开放出来。毕竟开源软件就好比能够进化的物种,提供你的DNA出来,让我们共同的软件变得越来越完美吧!
好了,废话不说了。播放器的全部源码本文中已经提供了下载地址。下面,我就其中我觉得可能值得关注的地方做一些解释。
一、VideoView与视频比例缩放:
以前在论坛上也看到有人问过如何实现视频按比例缩放的问题。的确,如果仅仅使用VideoView可能达不到我们想要达到的效果。这就需要我们对VideoView做一些改动,简单的说就是另外写一个类似VideoView的类出来(庆幸Android是开源的)。
我们可以很方便的获得VideoView的源代码,最简单的方法是直接在 GoogleCodeSearch上找“VideoView.java”。所以重写VideoView的过程其实只是在原来的基础上进行一些修改而已,并非一个很麻烦的工作。为什么Android自带的VideoView会保持视频的长宽比而不能让我们很方便的自定义比例呢?我猜想可能Google做Android也是一个很仓促的工程,许多代码并没有考虑得太成熟。
VideoView的源码中有这样一段代码:
1 @Override
2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 //Log.i("@@@@", "onMeasure");
4 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
5 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
6 if (mVideoWidth > 0 && mVideoHeight > 0) {
7 if ( mVideoWidth * height > width * mVideoHeight ) {
8 //Log.i("@@@", "image too tall, correcting");
9 height = width * mVideoHeight / mVideoWidth;
10 } else if ( mVideoWidth * height < width * mVideoHeight ) {
11 //Log.i("@@@", "image too wide, correcting");
12 width = height * mVideoWidth / mVideoHeight;
13 } else {
14 //Log.i("@@@", "aspect ratio is correct: " +
15 //width+"/"+height+"="+
16 //mVideoWidth+"/"+mVideoHeight);
17 }
18 }
19 //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
20 setMeasuredDimension(width, height);
21 }
22
这就是为什么长宽比不能改变的原因了。因为在OnMeasure的时候,就对这个长宽比进行了处理。
我们把其中处理的代码屏蔽掉,视频大小就可以随着VideoView的长宽改变而改变了。
1 @Override
2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 //Log.i("@@@@", "onMeasure");
4 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
5 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
6 /**//*if (mVideoWidth > 0 && mVideoHeight > 0) {
7 if ( mVideoWidth * height > width * mVideoHeight ) {
8 //Log.i("@@@", "image too tall, correcting");
9 height = width * mVideoHeight / mVideoWidth;
10 } else if ( mVideoWidth * height < width * mVideoHeight ) {
11 //Log.i("@@@", "image too wide, correcting");
12 width = height * mVideoWidth / mVideoHeight;
13 } else {
14 //Log.i("@@@", "aspect ratio is correct: " +
15 //width+"/"+height+"="+
16 //mVideoWidth+"/"+mVideoHeight);
17 }
18 }*/
19 //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
20 setMeasuredDimension(width,height);
21 }
二、视频控制菜单与播放界面的层次问题:
看到过一些别人写的视频播放器,其中有一些朋友老是简简单单的将VideoView和控制界面放在一个LinearLayout中。这样随着控制界面的出现与否,VideoView会随之改变长宽,给人的体验并不很好。所以,我认为VideoView和控制界面最好不要放在同一个层次上。不要偷懒,使用一个FrameLayout或者PopupWindow就可以解决这个问题。例如,我就简简单单地使用了PopupWindow,这个具体实现上,就百花争鸣吧。
三、视频文件扫描:
视频文件的扫描,现在想来主要有两种方式:
第一种就是直接读取媒体库中的视频文件数据库。当Android启动的时候,系统会自动扫描sdcard,并为媒体文件建立(或者更新)数据库。我们可以通过对应的URI来访问数据库,从而得到视频文件的列表:
1 private Uri videoListUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
2
3
4
5 Cursor cursor = getContentResolver().query(videoListUri, new String[]{"_display_name","_data"}, null, null, null);
6 int n = cursor.getCount();
7 cursor.moveToFirst();
8 LinkedList<MovieInfo> playList2 = new LinkedList<MovieInfo>();
9 for(int i = 0 ; i != n ; ++i){
10 MovieInfo mInfo = new MovieInfo();
11 mInfo.displayName = cursor.getString(cursor.getColumnIndex("_display_name"));
12 mInfo.path = cursor.getString(cursor.getColumnIndex("_data"));
13 playList2.add(mInfo);
14 cursor.moveToNext();
15 }
这种方法可能是最有效率的了,不过不知为何,媒体库中似乎没有扫描进本身支持的3GP视频格式(也可能我这里是一个特例) 。不过,正是因为这个原因,我才想到有可能需要另外一种最基本的扫描文件系统的方法来扫描视频文件。这就是文件系统的遍历:
1 private void getVideoFile(final LinkedList<MovieInfo> list,File file){
2
3 file.listFiles(new FileFilter(){
4
5 @Override
6 public boolean accept(File file) {
7 // TODO Auto-generated method stub
8 String name = file.getName();
9 int i = name.indexOf('.');
10 if(i != -1){
11 name = name.substring(i);
12 if(name.equalsIgnoreCase(".mp4")||name.equalsIgnoreCase(".3gp")){
13
14 MovieInfo mi = new MovieInfo();
15 mi.displayName = file.getName();
16 mi.path = file.getAbsolutePath();
17 list.add(mi);
18 return true;
19 }
20 }else if(file.isDirectory()){
21 getVideoFile(list, file);
22 }
23 return false;
24 }
25 });
26 }
当然,随着Android平台下的硬件设备越来越多,越来越强大。我们有理由相信,它以后将不仅仅只支持MP4和3GP格式的视频文件,所以我们必须使用两种方式结合的方法来获得最大的视频集合作为我们的视频列表。
四、播放过程中进度条progress的设定:
视频开始播放了,那么一个小麻烦出现了:什么时候设定进度条才更有效率?我这里有一种方法供大家参考,那就是通过Handler自己给自己发消息来达到不断设置进度条的目的。
1 Handler myHandler = new Handler(){
2
3 @Override
4 public void handleMessage(Message msg) {
5 // TODO Auto-generated method stub
6
7 switch(msg.what){
8
9 case PROGRESS_CHANGED:
10
11 int i = vv.getCurrentPosition();
12 seekBar.setProgress(i);
13
14 i/=1000;
15 int minute = i/60;
16 int hour = minute/60;
17 int second = i%60;
18 minute %= 60;
19 playedTextView.setText(String.format("%02d:%02d:%02d", hour,minute,second));
20
21 sendEmptyMessage(PROGRESS_CHANGED);
22 break;
23
当然,这种方法,需要首先发送一个初始消息来启动。
五、全屏与非全屏:
大家都知道,一般一个Activity设置全屏的方法有两种,一是在OnCreate中:
1 @Override
2 public void onCreate(Bundle icicle) {
3 super.onCreate(icicle);
4
5 requestWindowFeature(Window.FEATURE_NO_TITLE);
6 Window win = getWindow();
7 win.setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,
8 WindowManager.LayoutParams.NO_STATUS_BAR_FLAG);
9
10 setContentView(R.layout.mylayout);
11
12
13
二是在AndroidManifest.xml中:
1<activity android:name=".MyActivity"
2 android:label=""
3 android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
然而,这两种方法都不能达到我们在视频播放过程中设置全屏与否的目的。因为它们都只能在初始化的时候决定全屏与否。那么我现在要说的就是第三种方法:
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));
好了,就写这些了吧。可能这些知识有人知道,或者还有些盲点我没有讲到。欢迎大家与我联系,大家一起多多讨论交流,并且整个源码都开放出来了,大家一定可以把来龙去脉弄得一清二楚的!最后,多谢大家听我罗嗦,欢迎使用SeeJoPlayer,欢迎阅读其源码!本文也欢迎大家转载,不过转载请注明出处: http://www.blogjava.net/zh-weir/archive/2010/01/24/310617.html