android 视频+音频播放器Demo

程序主界面

  • MainActivity.java

    1.主界面,头部是两个TextView(自定义类似指针效果),底部是ViewPager。ViewPager中每个页面对应的是一个Fragment.这样就搭起了首页。

    xml文件代码:

   

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     android:background="@mipmap/base_bg"  
  8.     tools:context=".activity.MainActivity">  
  9.   
  10.     <LinearLayout  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="55dp"  
  13.         android:orientation="vertical"  
  14.         android:background="@mipmap/base_titlebar_bg"  
  15.         >  
  16.   
  17.         <LinearLayout  
  18.             android:layout_width="match_parent"  
  19.             android:layout_height="0dp"  
  20.             android:layout_weight="1"  
  21.             android:orientation="horizontal"  
  22.             android:gravity="center_vertical"  
  23.             >  
  24.   
  25.             <TextView  
  26.                 android:id="@+id/tv_audio"  
  27.                 style="@style/mainActivity_indicator"  
  28.                 android:text="@string/audio"  
  29.             />  
  30.   
  31.             <TextView  
  32.                 android:id="@+id/music"  
  33.                 style="@style/mainActivity_indicator"  
  34.                 android:text="@string/music" />  
  35.   
  36.         </LinearLayout>  
  37.   
  38.         <View  
  39.             android:id="@+id/indicator"  
  40.             android:layout_width="30dp"  
  41.             android:layout_height="3dp"  
  42.             android:background="@color/green"  
  43.             />  
  44.   
  45.     </LinearLayout>  
  46.   
  47.     <android.support.v4.view.ViewPager  
  48.         android:id="@+id/view_pager"  
  49.         android:layout_width="match_parent"  
  50.         android:layout_height="wrap_content"/>  
  51. </LinearLayout>  
  2.当选中音频或者视频时,TextView的文字颜色和大小都改变。

[java]  view plain  copy
  1. /** 
  2.      * 选择:音频 / 音乐 
  3.      * @param isAudio 是选择了音频 
  4.      */  
  5.     public void changeSelectedIndicator(boolean isAudio){  
  6.   
  7.         mAudio.setSelected(isAudio);  
  8.         mMusic.setSelected(!isAudio);  
  9.   
  10.         float ascale = isAudio ? 1.2f : 1.0f;  
  11.         float mscale = isAudio ? 1.0f : 1.2f;  
  12.   
  13.   
  14.         ViewPropertyAnimator.animate(mAudio).scaleX(ascale);  
  15.         ViewPropertyAnimator.animate(mAudio).scaleY(ascale);  
  16.   
  17.         ViewPropertyAnimator.animate(mMusic).scaleX(mscale);  
  18.         ViewPropertyAnimator.animate(mMusic).scaleY(mscale);  
  19.     }  
 3.滑动指示先与ViewPager的结合。

 

[java]  view plain  copy
  1. <span style="color:#000000;">/** 
  2.      * 滑动指示线 
  3.      * @param position 
  4.      * @param positionOffsetPixels 
  5.      */  
  6.     protected void scrollIndicator(int position, int positionOffsetPixels) {  
  7.         int translationX = mIndicatorWidth * position + positionOffsetPixels / pageSize ;  
  8.         LogUtil.i("qd","translationX =="+translationX );  
  9.         ViewHelper.setTranslationX(mIndicator, translationX);  
  10.   
  11.     }  
  12.    
  13. </span><pre name="code" class="java"><span style="color:#000000;"> mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {  
  14.             @Override  
  15.             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {  
  16.                 LogUtil.i("qd""position===" + position + "  positionOffset=" + positionOffset + "  positionOffsetPixels=" + positionOffsetPixels);  
  17.                 scrollIndicator(position, positionOffsetPixels);  
  18.             }  
  19.   
  20.             @Override  
  21.             public void onPageSelected(int position) {  
  22.                 changeSelectedIndicator(position == 0);  
  23.             }  
  24.   
  25.             @Override  
  26.             public void onPageScrollStateChanged(int state) {  
  27.   
  28.             }  
  29.         });</span>  

 
4,给ViewPager设置FragmentStatePagerAdapter,他的构造需要FragmentManager,所以MainActivity需要继承自FragmentActivity 
   
[java]  view plain  copy
  1. fragmentAdapter.addItem(new VideoFragment());  
  2. fragmentAdapter.addItem(new MusicFragment());  

  • VideoFragment.java(视频模块)
    新建了BaseFragment这样一个基类。设计思想,1.视频和音频的列表页面其实大致相同,只是显示的具体数据不同,所以可以他他们共同的东西抽取出来成为一个  

[java]  view plain  copy
  1. public abstract class BaseFragment<T> extends Fragment implements BaseInterface  
    2.每个类都会涉及到,初始化view,初始化data,设置监听等。所以,把这三个方法涉及成一个Interface,由每个需要的类来进行实现。

    3.每个Fragment都会执行onCreateView,只是他们的布局不同,抽取出getLayoutId(),返回每个子类具体的布局。并且在onViewCreated方法中,调用initView,initData.

      这样子类只需要实现这几个方法即可。

   4.如何查询手机当中的视频? 音频资源???

     Android将这些的信息都封装进了本地数据库中,我们只需要按照指定的格式查询数据库即可。

     android 提供了异步查询数据库的一个类,AsyncQueryHandler,他使用了内容观察者模式,每次数据库发生变动时,他返回的Cursor都会变化。

    

[java]  view plain  copy
  1. <span style="font-size:18px;"> AsyncQueryHandler task = new AsyncQueryHandler(getActivity().getContentResolver()) {  
  2.                 @Override  
  3.                 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {  
  4.                     super.onQueryComplete(token, cookie, cursor);  
  5.                     if(cursor != null){  
  6.                         mListView.setAdapter(getAdapter(getActivity(), cursor));  
  7.                     }else{  
  8.                         LogUtil.i("qd","audioFragment cursor == null");  
  9.                     }  
  10.                 }  
  11.             };  
  12. task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());  
  13. </span>  
  音频和视频的区别就在于这些参数上
[java]  view plain  copy
  1. <span style="font-size:18px;"> task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());</span>  
  所以,我们抽取成abstract方法,让具体的子类来实现。

  音频的地址:

[java]  view plain  copy
  1. <span style="font-size:18px;">MediaStore.Audio.Media.EXTERNAL_CONTENT_URI</span>  
  视频的地址:  

[java]  view plain  copy
  1. <span style="font-size:18px;">MediaStore.Video.Media.EXTERNAL_CONTENT_URI</span>  
 5.查询成功后,会返回一个cursor,其中包括了数据,将数据设置到adapter中,而且这个adapter需要继承自CursorAdapter
[java]  view plain  copy
  1. <span style="font-size:18px;">public class VideoAdapter extends CursorAdapter {  
  2.     public VideoAdapter(Context context, Cursor c) {  
  3.         super(context, c);  
  4.     }  
  5.   
  6.     @Override  
  7.     public View newView(Context context, Cursor cursor, ViewGroup parent) {  
  8.         View view = LayoutInflater.from(context).inflate(R.layout.video_list_item, null);  
  9.         ViewHolder holder = new ViewHolder();  
  10.         holder.title = (TextView) view.findViewById(R.id.title);  
  11.         holder.duration = (TextView) view.findViewById(R.id.duration);  
  12.         holder.size = (TextView) view.findViewById(R.id.size);  
  13.         view.setTag(holder);  
  14.         return view;  
  15.     }  
  16.   
  17.     @Override  
  18.     public void bindView(View view, Context context, Cursor cursor) {  
  19.         ViewHolder holder = (ViewHolder) view.getTag();  
  20.         VideoItem videoItem = VideoItem.fromCursor(cursor);  
  21.         holder.title.setText(videoItem.getTitle());  
  22.         holder.duration.setText(TimeUtil.formatLong(videoItem.getDuration()));  
  23.         holder.size.setText(Formatter.formatFileSize(context, videoItem.getSize()));  
  24.     }  
  25.   
  26.     class ViewHolder{  
  27.         public TextView title;  
  28.         public TextView duration;  
  29.         public TextView size;  
  30.     }  
  31. }  
  32. </span>  
 6.最后设置当点击条目的时候,页面进行跳转。将条目的位置和查询到的所有数据全都传递过去。

  •  音频播放页面

    利用了android中的VideoView来进行播放视频。

    主要涉及到了一下几个方法。 

[java]  view plain  copy
  1. <span style="font-size:18px;">//1.设置播放路径  
  2. mVideoView.setVideoPath(item.getPath());  
  3. </span>  
[java]  view plain  copy
  1. <span style="font-size:18px;">//2.视频准备好的监听  
  2. mVideoView.setOnPreparedListener(preparedListener);  
  3. </span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener(){  
  4.   
  5.         @Override  
  6.         public void onPrepared(MediaPlayer mp) {  
  7.             //3.切记需要,先启动,因为接下来需要获取他播放的位置,否在的话会报异常。  
  8.             mVideoView.start();  
  9.             if(item != null){  
  10.                 mTitle.setText(item.getTitle());  
  11.             }  
  12.   
  13.             mDuration.setText(TimeUtil.formatLong(mVideoView.getDuration()));  
  14.             mSeekBarVideo.setMax((int) </span><pre name="code" class="java"><span style="font-size:18px;">mVideoView.getDuration()</span>  
); updateVideoCurrentPosition(); LogUtil.i("qd", "preparedListener selectPosition===" + selectPosition); loading.setVisibility(View.GONE); } };
 
 
4.获取视频的播放时长和当前时长 
[java]  view plain  copy
  1. <span style="font-size:18px;">mVideoView.getDuration()</span>  
[java]  view plain  copy
  1. <span style="font-size:18px;">mVideoView.getCurrentPosition()</span>  
5.设置视频播放前的监听
[java]  view plain  copy
  1. <span style="font-size:18px;">//缓冲视频前的准备  
  2. mVideoView.setOnInfoListener(onInfoListener);  
  3. </span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener(){  
  4.   
  5.         @Override  
  6.         public boolean onInfo(MediaPlayer mp, int what, int extra) {  
  7.             switch (what){  
  8.                 case MediaPlayer.MEDIA_INFO_BUFFERING_START:  //缓冲开始,设置加载页面可见  
  9.                     loading.setVisibility(View.VISIBLE);  
  10.                     break;  
  11.                 case MediaPlayer.MEDIA_INFO_BUFFERING_END: //缓冲结束,设置加载页面不可见  
  12.                     loading.setVisibility(View.GONE);  
  13.                     break;  
  14.             }  
  15.             return false;  
  16.         }  
  17.     };</span>  
 
6.设置视频缓存(针对网络视频,这个方法是用来更新第二进度条的) 
[java]  view plain  copy
  1. <span style="font-size:18px;"//视频缓冲  ----本地视频不存在缓冲一说  
  2. mVideoView.setOnBufferingUpdateListener(onBufferingUpdateListener);  
  3. </span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener(){  
  4.   
  5.         @Override  
  6.         public void onBufferingUpdate(MediaPlayer mp, int percent) {  
  7.             float f = (float)percent / 100;  
  8.             int secondaryProgress = (int) (f * mVideoView.getDuration());  
  9.             mSeekBarVideo.setSecondaryProgress(secondaryProgress);  
  10.         }  
  11.     };</span>  
 
7.设置视频播放完的监听 
[java]  view plain  copy
  1. <span style="font-size:18px;"//视频播放完的监听  
  2.  mVideoView.setOnCompletionListener(completionListener);  
  3. </span><pre name="code" class="java"><span style="font-size:18px;"> MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener(){  
  4.         @Override  
  5.         public void onCompletion(MediaPlayer mp) {  
  6.             mhandler.removeMessages(UPDATE_VIDEO_CURRENT_POSITION);  
  7.         }  
  8.     };</span>  
 
8.当对屏幕进行手势识别的时候,用GestureDetector,在onTouch事件时,交给GestureDetector的onTouchEvent来处理。 
 

  • 万能视频播放器
   主要用开源的Vitamio来实现。

    1.首先,在清单文件中加入

[java]  view plain  copy
  1. <span style="font-size:18px;"> <activity  
  2.             android:name="io.vov.vitamio.activity.InitActivity"  
  3.             android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"  
  4.             android:launchMode="singleTop"  
  5.             android:theme="@android:style/Theme.NoTitleBar"  
  6.             android:windowSoftInputMode="stateAlwaysHidden" />  
  7.   
  8.   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>  
  9.     <uses-permission android:name="android.permission.INTERNET"/>  
  10.     <uses-permission android:name="android.permission.WAKE_LOCK" />  
  11.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
  12.     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
  13. </span>  
  2.在VideoPlayerActivity.java(视频播放页面)初始化数据时,加入
[java]  view plain  copy
  1. <span style="font-size:18px;">  // 初始化Vitamio SDK  
  2.         if (!LibsChecker.checkVitamioLibs(this))  
  3.             return;</span>  
 3.替换VideoView,成Vitamio的中的VideoView,在个类的路径
[java]  view plain  copy
  1. <span style="font-size:18px;">io.vov.vitamio.widget.VideoView</span>  
这样就可以播放基本上所有类型的视频文件了,android默认只支持mp4格式的视频。


  • 音频模块(AudioPlayerActivity.java  和 AudioPlayerService.java) 播放音乐需要服务。

   Activity与Service如何交互?

   1.使用AIDL

   2.使用Messenger   这里主要讲解下次方式。

   有点像,TCP/IP通讯,需要3此握手,才能建立连接、

   第一步: 

[java]  view plain  copy
  1. <span style="font-size:18px;">//开启服务  
  2.  startService(service);  
  3. //绑定服务,这样才能与其交互  
  4. bindService(service, conn, BIND_AUTO_CREATE);  
  5. </span>  
 第二步:
[java]  view plain  copy
  1. <span style="font-size:18px;"//上面的开启服务方式,首先调用onStartCommand,然后调用onBind()  
  2.  @Override  
  3.  public IBinder onBind(Intent intent) {  
  4.        return serviceMessenger.getBinder();  
  5.  }  
  6.  //在service中创建出一个信使,用来与Activity通信。</span><pre name="code" class="java"><span style="font-size:18px;">private Messenger serviceMessenger = new Messenger(mHandler);</span>  
//创建handler,用来处理此信使获取到的信息
[java]  view plain  copy
  1. <span style="font-size:18px;">private Handler mHandler = new Handler(){  
  2.         @Override  
  3.         public void handleMessage(Message msg) {  
  4.         }  
  5.     };</span>  
 
第三步: 
 

[java]  view plain  copy
  1. <span style="font-size:18px;">//在Activty中,当service与Activty连接上时会回调此方法。   
  2. ServiceConnection conn = new ServiceConnection(){  
  3.   
  4.         @Override  
  5.         public void onServiceConnected(ComponentName name, IBinder service) {  
  6.             //获取service传递过来的信使  
  7.             Messenger serviceMessenger = new Messenger(service);  
  8.             //创建一条消息  
  9.             Message message = Message.obtain(null, AudioPlayService.WHAT_UI_INTEFACE);  
  10.             //携带UI类过去  
  11.             message.obj = AudioPlayerActivity.this;  
  12.             //告诉service,此UI的信使是哪个(这样,service就能拿到ui的信使,并用此信使,发送消息)  
  13.             message.replyTo = uiMessenger;  
  14.             //用service的信使,给service发送消息  
  15.             try {  
  16.                 serviceMessenger.send(message);  
  17.             } catch (RemoteException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.         }  
  21.   
  22.         @Override  
  23.         public void onServiceDisconnected(ComponentName name) {  
  24.   
  25.         }  
  26.     };</span>  
第四步:
[java]  view plain  copy
  1. <span style="font-size:18px;">//service中,处理自己的信使收到的消息  
  2. private Handler mHandler = new Handler(){  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             switch (msg.what){  
  6.                 case WHAT_UI_INTEFACE:  
  7.                     //拿到,传递过来的UI对象  
  8.                     audioUI = (AudioUI) msg.obj;  
  9.                     //获取ui对象的信使  
  10.                     Messenger uiMessenger = msg.replyTo;  
  11.                     //创建消息  
  12.                     Message message = Message.obtain(null, WHAT_PLAYSERVICE_INTERFACCE);  
  13.                     //把service传递过去  
  14.                     message.obj = AudioPlayService.this;  
  15.                     //  
  16.                     message.arg1 = flag ;  
  17.                     //用UI类的信使,发送消息  
  18.                     try {  
  19.                         uiMessenger.send(message);  
  20.                     } catch (RemoteException e) {  
  21.                         e.printStackTrace();  
  22.                     }  
  23.                     break;  
  24.             }  
  25.             super.handleMessage(msg);  
  26.         }  
  27.     };  
  28. </span>  
第五步:

service中拿到了Activity中的信使,所以用此信使传递消息。

[java]  view plain  copy
  1. <span style="font-size:18px;">//这样在Ativity中又收到了service的消息,这样即确定了Service与Activity建立了可靠的连接。  
  2. private Handler mHandler = new Handler(){  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             switch (msg.what){  
  6.                 case AudioPlayService.WHAT_PLAYSERVICE_INTERFACCE:  
  7.                     //Activity 进行了三次握手,创建了连接  
  8.                     audioPlayService = (IPlayAudio) msg.obj;  
  9.   
  10.                     if(msg.arg1 == -1){  
  11.                         //开启音频  
  12.                         audioPlayService.openAudio();  
  13.                     }else{  
  14.                         //从通知栏,点击进来额,机选刷新activity并不做其他处理  
  15.                         updateUI(audioPlayService.getCurrentMusicItem());  
  16.                     }  
  17.   
  18.                     break;  
  19.                 case UPDATE_PLAY_TIME:  
  20.                     updatePlayTime();  
  21.                     break;  
  22.                 case UPDATE_LYRICS:  
  23.                     updateLyrics();  
  24.                     break;  
  25.             }  
  26.             super.handleMessage(msg);  
  27.         }  
  28.     };</span>  

这样就可以在service,activity中拿上对方的引用,来操作对方的方法,



视频  / 音频效果:



我开发中遇到的坑:

1.音频,视频的开发过程,基本不怎么报错,直接奔溃,要么是ANR,所以一定要细心,在加上Debug进行调试

2.控制台报错:client not yet。。。
        Connected to the target VM, address: 'localhost:8603', transport: 'socket'
解决方案:其实还是模棱两可,碰出来的。
      重新设置断点,然后在进行调试。
      注意查看运行时是否是app(有时候是Activity)

3.studio开发当涉及到.so文件的正确导入方式

在gradle文件中加入下面这句话:
 sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

4.int类型的两个数,想出得到一个float类型的数值时,需要将除数,或者被除数转换为

float类型,否在得到的除数将== 0;


5.service生命周期
1.startService --> oncreate  ---> onStartCommand  --->onDestroy(必须调用stopService!!!!!此服务才能结束,否则再次打开应用的时候,可能出现ANR)

2.bindService -->  onCreate ---> onBind  -- > unUnBind() --> onDestroy
  在activity销毁的时候,必须显示的调用unBindServce来接触绑定,销毁service

3.混合启动service   startService --> bindService
  ---> 在activity销毁的时候,必须调用stopService()来停止服务,首先会调用onUnBind,在调用onDestroy
  ---> 只调用onUnbind()并不会关闭服务。 startService,必须stopService才能关闭掉
  ---> 如果unBindService,stopService都调用的话onUnbind ,onDestroy会按顺序调用一次。

我在开发音乐播放器的时候,在activity销毁的时候没有关闭service,下次再启动应用的时候出现白屏(也就是应用不能开启,最后会报ANR)


下载地址:

https://github.com/QDqiaodong/VideoAndAudio


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值