android 视频+音频播放器Demo
2017-03-06 15:41
363 查看
程序主界面
MainActivity.java
1.主界面,头部是两个TextView(自定义类似指针效果),底部是ViewPager。ViewPager中每个页面对应的是一个Fragment.这样就搭起了首页。
xml文件代码:
[html] view
plain copy
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/base_bg"
tools:context=".activity.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:orientation="vertical"
android:background="@mipmap/base_titlebar_bg"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<TextView
android:id="@+id/tv_audio"
style="@style/mainActivity_indicator"
android:text="@string/audio"
/>
<TextView
android:id="@+id/music"
style="@style/mainActivity_indicator"
android:text="@string/music" />
</LinearLayout>
<View
android:id="@+id/indicator"
android:layout_width="30dp"
android:layout_height="3dp"
android:background="@color/green"
/>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
2.当选中音频或者视频时,TextView的文字颜色和大小都改变。
[java] view
plain copy
/**
* 选择:音频 / 音乐
* @param isAudio 是选择了音频
*/
public void changeSelectedIndicator(boolean isAudio){
mAudio.setSelected(isAudio);
mMusic.setSelected(!isAudio);
float ascale = isAudio ? 1.2f : 1.0f;
float mscale = isAudio ? 1.0f : 1.2f;
ViewPropertyAnimator.animate(mAudio).scaleX(ascale);
ViewPropertyAnimator.animate(mAudio).scaleY(ascale);
ViewPropertyAnimator.animate(mMusic).scaleX(mscale);
ViewPropertyAnimator.animate(mMusic).scaleY(mscale);
}
3.滑动指示先与ViewPager的结合。
[java] view
plain copy
<span style="color:#000000;">/**
* 滑动指示线
* @param position
* @param positionOffsetPixels
*/
protected void scrollIndicator(int position, int positionOffsetPixels) {
int translationX = mIndicatorWidth * position + positionOffsetPixels / pageSize ;
LogUtil.i("qd","translationX =="+translationX );
ViewHelper.setTranslationX(mIndicator, translationX);
}
</span><pre name="code" class="java"><span style="color:#000000;"> mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
LogUtil.i("qd", "position===" + position + " positionOffset=" + positionOffset + " positionOffsetPixels=" + positionOffsetPixels);
scrollIndicator(position, positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
changeSelectedIndicator(position == 0);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});</span>
4,给ViewPager设置FragmentStatePagerAdapter,他的构造需要FragmentManager,所以MainActivity需要继承自FragmentActivity
[java] view
plain copy
fragmentAdapter.addItem(new VideoFragment());
fragmentAdapter.addItem(new MusicFragment());
VideoFragment.java(视频模块)
新建了BaseFragment这样一个基类。设计思想,1.视频和音频的列表页面其实大致相同,只是显示的具体数据不同,所以可以他他们共同的东西抽取出来成为一个
[java] view
plain copy
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
<span style="font-size:18px;"> AsyncQueryHandler task = new AsyncQueryHandler(getActivity().getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
if(cursor != null){
mListView.setAdapter(getAdapter(getActivity(), cursor));
}else{
LogUtil.i("qd","audioFragment cursor == null");
}
}
};
task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());
</span>
音频和视频的区别就在于这些参数上
[java] view
plain copy
<span style="font-size:18px;"> task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());</span>
所以,我们抽取成abstract方法,让具体的子类来实现。
音频的地址:
[java] view
plain copy
<span style="font-size:18px;">MediaStore.Audio.Media.EXTERNAL_CONTENT_URI</span>
视频的地址:
[java] view
plain copy
<span style="font-size:18px;">MediaStore.Video.Media.EXTERNAL_CONTENT_URI</span>
5.查询成功后,会返回一个cursor,其中包括了数据,将数据设置到adapter中,而且这个adapter需要继承自CursorAdapter
[java] view
plain copy
<span style="font-size:18px;">public class VideoAdapter extends CursorAdapter {
public VideoAdapter(Context context, Cursor c) {
super(context, c);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(R.layout.video_list_item, null);
ViewHolder holder = new ViewHolder();
holder.title = (TextView) view.findViewById(R.id.title);
holder.duration = (TextView) view.findViewById(R.id.duration);
holder.size = (TextView) view.findViewById(R.id.size);
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
VideoItem videoItem = VideoItem.fromCursor(cursor);
holder.title.setText(videoItem.getTitle());
holder.duration.setText(TimeUtil.formatLong(videoItem.getDuration()));
holder.size.setText(Formatter.formatFileSize(context, videoItem.getSize()));
}
class ViewHolder{
public TextView title;
public TextView duration;
public TextView size;
}
}
</span>
6.最后设置当点击条目的时候,页面进行跳转。将条目的位置和查询到的所有数据全都传递过去。
音频播放页面
利用了android中的VideoView来进行播放视频。
主要涉及到了一下几个方法。
[java] view
plain copy
<span style="font-size:18px;">//1.设置播放路径
mVideoView.setVideoPath(item.getPath());
</span>
[java] view
plain copy
<span style="font-size:18px;">//2.视频准备好的监听
mVideoView.setOnPreparedListener(preparedListener);
</span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mp) {
//3.切记需要,先启动,因为接下来需要获取他播放的位置,否在的话会报异常。
mVideoView.start();
if(item != null){
mTitle.setText(item.getTitle());
}
mDuration.setText(TimeUtil.formatLong(mVideoView.getDuration()));
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
<span style="font-size:18px;">mVideoView.getDuration()</span>
[java] view
plain copy
<span style="font-size:18px;">mVideoView.getCurrentPosition()</span>
5.设置视频播放前的监听
[java] view
plain copy
<span style="font-size:18px;">//缓冲视频前的准备
mVideoView.setOnInfoListener(onInfoListener);
</span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener(){
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what){
case MediaPlayer.MEDIA_INFO_BUFFERING_START: //缓冲开始,设置加载页面可见
loading.setVisibility(View.VISIBLE);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END: //缓冲结束,设置加载页面不可见
loading.setVisibility(View.GONE);
break;
}
return false;
}
};</span>
6.设置视频缓存(针对网络视频,这个方法是用来更新第二进度条的)
[java] view
plain copy
<span style="font-size:18px;"> //视频缓冲 ----本地视频不存在缓冲一说
mVideoView.setOnBufferingUpdateListener(onBufferingUpdateListener);
</span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener(){
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
float f = (float)percent / 100;
int secondaryProgress = (int) (f * mVideoView.getDuration());
mSeekBarVideo.setSecondaryProgress(secondaryProgress);
}
};</span>
7.设置视频播放完的监听
[java] view
plain copy
<span style="font-size:18px;"> //视频播放完的监听
mVideoView.setOnCompletionListener(completionListener);
</span><pre name="code" class="java"><span style="font-size:18px;"> MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener(){
@Override
public void onCompletion(MediaPlayer mp) {
mhandler.removeMessages(UPDATE_VIDEO_CURRENT_POSITION);
}
};</span>
8.当对屏幕进行手势识别的时候,用GestureDetector,在onTouch事件时,交给GestureDetector的onTouchEvent来处理。
万能视频播放器
主要用开源的Vitamio来实现。
1.首先,在清单文件中加入
[java] view
plain copy
<span style="font-size:18px;"> <activity
android:name="io.vov.vitamio.activity.InitActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:launchMode="singleTop"
android:theme="@android:style/Theme.NoTitleBar"
android:windowSoftInputMode="stateAlwaysHidden" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</span>
2.在VideoPlayerActivity.java(视频播放页面)初始化数据时,加入
[java] view
plain copy
<span style="font-size:18px;"> // 初始化Vitamio SDK
if (!LibsChecker.checkVitamioLibs(this))
return;</span>
3.替换VideoView,成Vitamio的中的VideoView,在个类的路径
[java] view
plain copy
<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
<span style="font-size:18px;">//开启服务
startService(service);
//绑定服务,这样才能与其交互
bindService(service, conn, BIND_AUTO_CREATE);
</span>
第二步:
[java] view
plain copy
<span style="font-size:18px;"> //上面的开启服务方式,首先调用onStartCommand,然后调用onBind()
@Override
public IBinder onBind(Intent intent) {
return serviceMessenger.getBinder();
}
//在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
<span style="font-size:18px;">private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
}
};</span>
第三步:
[java] view
plain copy
<span style="font-size:18px;">//在Activty中,当service与Activty连接上时会回调此方法。
ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取service传递过来的信使
Messenger serviceMessenger = new Messenger(service);
//创建一条消息
Message message = Message.obtain(null, AudioPlayService.WHAT_UI_INTEFACE);
//携带UI类过去
message.obj = AudioPlayerActivity.this;
//告诉service,此UI的信使是哪个(这样,service就能拿到ui的信使,并用此信使,发送消息)
message.replyTo = uiMessenger;
//用service的信使,给service发送消息
try {
serviceMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};</span>
第四步:
[java] view
plain copy
<span style="font-size:18px;">//service中,处理自己的信使收到的消息
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case WHAT_UI_INTEFACE:
//拿到,传递过来的UI对象
audioUI = (AudioUI) msg.obj;
//获取ui对象的信使
Messenger uiMessenger = msg.replyTo;
//创建消息
Message message = Message.obtain(null, WHAT_PLAYSERVICE_INTERFACCE);
//把service传递过去
message.obj = AudioPlayService.this;
//
message.arg1 = flag ;
//用UI类的信使,发送消息
try {
uiMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msg);
}
};
</span>
第五步:
service中拿到了Activity中的信使,所以用此信使传递消息。
[java] view
plain copy
<span style="font-size:18px;">//这样在Ativity中又收到了service的消息,这样即确定了Service与Activity建立了可靠的连接。
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case AudioPlayService.WHAT_PLAYSERVICE_INTERFACCE:
//Activity 进行了三次握手,创建了连接
audioPlayService = (IPlayAudio) msg.obj;
if(msg.arg1 == -1){
//开启音频
audioPlayService.openAudio();
}else{
//从通知栏,点击进来额,机选刷新activity并不做其他处理
updateUI(audioPlayService.getCurrentMusicItem());
}
break;
case UPDATE_PLAY_TIME:
updatePlayTime();
break;
case UPDATE_LYRICS:
updateLyrics();
break;
}
super.handleMessage(msg);
}
};</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
MainActivity.java
1.主界面,头部是两个TextView(自定义类似指针效果),底部是ViewPager。ViewPager中每个页面对应的是一个Fragment.这样就搭起了首页。
xml文件代码:
[html] view
plain copy
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/base_bg"
tools:context=".activity.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:orientation="vertical"
android:background="@mipmap/base_titlebar_bg"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<TextView
android:id="@+id/tv_audio"
style="@style/mainActivity_indicator"
android:text="@string/audio"
/>
<TextView
android:id="@+id/music"
style="@style/mainActivity_indicator"
android:text="@string/music" />
</LinearLayout>
<View
android:id="@+id/indicator"
android:layout_width="30dp"
android:layout_height="3dp"
android:background="@color/green"
/>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
2.当选中音频或者视频时,TextView的文字颜色和大小都改变。
[java] view
plain copy
/**
* 选择:音频 / 音乐
* @param isAudio 是选择了音频
*/
public void changeSelectedIndicator(boolean isAudio){
mAudio.setSelected(isAudio);
mMusic.setSelected(!isAudio);
float ascale = isAudio ? 1.2f : 1.0f;
float mscale = isAudio ? 1.0f : 1.2f;
ViewPropertyAnimator.animate(mAudio).scaleX(ascale);
ViewPropertyAnimator.animate(mAudio).scaleY(ascale);
ViewPropertyAnimator.animate(mMusic).scaleX(mscale);
ViewPropertyAnimator.animate(mMusic).scaleY(mscale);
}
3.滑动指示先与ViewPager的结合。
[java] view
plain copy
<span style="color:#000000;">/**
* 滑动指示线
* @param position
* @param positionOffsetPixels
*/
protected void scrollIndicator(int position, int positionOffsetPixels) {
int translationX = mIndicatorWidth * position + positionOffsetPixels / pageSize ;
LogUtil.i("qd","translationX =="+translationX );
ViewHelper.setTranslationX(mIndicator, translationX);
}
</span><pre name="code" class="java"><span style="color:#000000;"> mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
LogUtil.i("qd", "position===" + position + " positionOffset=" + positionOffset + " positionOffsetPixels=" + positionOffsetPixels);
scrollIndicator(position, positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
changeSelectedIndicator(position == 0);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});</span>
4,给ViewPager设置FragmentStatePagerAdapter,他的构造需要FragmentManager,所以MainActivity需要继承自FragmentActivity
[java] view
plain copy
fragmentAdapter.addItem(new VideoFragment());
fragmentAdapter.addItem(new MusicFragment());
VideoFragment.java(视频模块)
新建了BaseFragment这样一个基类。设计思想,1.视频和音频的列表页面其实大致相同,只是显示的具体数据不同,所以可以他他们共同的东西抽取出来成为一个
[java] view
plain copy
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
<span style="font-size:18px;"> AsyncQueryHandler task = new AsyncQueryHandler(getActivity().getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
if(cursor != null){
mListView.setAdapter(getAdapter(getActivity(), cursor));
}else{
LogUtil.i("qd","audioFragment cursor == null");
}
}
};
task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());
</span>
音频和视频的区别就在于这些参数上
[java] view
plain copy
<span style="font-size:18px;"> task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());</span>
所以,我们抽取成abstract方法,让具体的子类来实现。
音频的地址:
[java] view
plain copy
<span style="font-size:18px;">MediaStore.Audio.Media.EXTERNAL_CONTENT_URI</span>
视频的地址:
[java] view
plain copy
<span style="font-size:18px;">MediaStore.Video.Media.EXTERNAL_CONTENT_URI</span>
5.查询成功后,会返回一个cursor,其中包括了数据,将数据设置到adapter中,而且这个adapter需要继承自CursorAdapter
[java] view
plain copy
<span style="font-size:18px;">public class VideoAdapter extends CursorAdapter {
public VideoAdapter(Context context, Cursor c) {
super(context, c);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(R.layout.video_list_item, null);
ViewHolder holder = new ViewHolder();
holder.title = (TextView) view.findViewById(R.id.title);
holder.duration = (TextView) view.findViewById(R.id.duration);
holder.size = (TextView) view.findViewById(R.id.size);
view.setTag(holder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
VideoItem videoItem = VideoItem.fromCursor(cursor);
holder.title.setText(videoItem.getTitle());
holder.duration.setText(TimeUtil.formatLong(videoItem.getDuration()));
holder.size.setText(Formatter.formatFileSize(context, videoItem.getSize()));
}
class ViewHolder{
public TextView title;
public TextView duration;
public TextView size;
}
}
</span>
6.最后设置当点击条目的时候,页面进行跳转。将条目的位置和查询到的所有数据全都传递过去。
音频播放页面
利用了android中的VideoView来进行播放视频。
主要涉及到了一下几个方法。
[java] view
plain copy
<span style="font-size:18px;">//1.设置播放路径
mVideoView.setVideoPath(item.getPath());
</span>
[java] view
plain copy
<span style="font-size:18px;">//2.视频准备好的监听
mVideoView.setOnPreparedListener(preparedListener);
</span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(MediaPlayer mp) {
//3.切记需要,先启动,因为接下来需要获取他播放的位置,否在的话会报异常。
mVideoView.start();
if(item != null){
mTitle.setText(item.getTitle());
}
mDuration.setText(TimeUtil.formatLong(mVideoView.getDuration()));
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
<span style="font-size:18px;">mVideoView.getDuration()</span>
[java] view
plain copy
<span style="font-size:18px;">mVideoView.getCurrentPosition()</span>
5.设置视频播放前的监听
[java] view
plain copy
<span style="font-size:18px;">//缓冲视频前的准备
mVideoView.setOnInfoListener(onInfoListener);
</span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener(){
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what){
case MediaPlayer.MEDIA_INFO_BUFFERING_START: //缓冲开始,设置加载页面可见
loading.setVisibility(View.VISIBLE);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END: //缓冲结束,设置加载页面不可见
loading.setVisibility(View.GONE);
break;
}
return false;
}
};</span>
6.设置视频缓存(针对网络视频,这个方法是用来更新第二进度条的)
[java] view
plain copy
<span style="font-size:18px;"> //视频缓冲 ----本地视频不存在缓冲一说
mVideoView.setOnBufferingUpdateListener(onBufferingUpdateListener);
</span><pre name="code" class="java"><span style="font-size:18px;">MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener(){
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
float f = (float)percent / 100;
int secondaryProgress = (int) (f * mVideoView.getDuration());
mSeekBarVideo.setSecondaryProgress(secondaryProgress);
}
};</span>
7.设置视频播放完的监听
[java] view
plain copy
<span style="font-size:18px;"> //视频播放完的监听
mVideoView.setOnCompletionListener(completionListener);
</span><pre name="code" class="java"><span style="font-size:18px;"> MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener(){
@Override
public void onCompletion(MediaPlayer mp) {
mhandler.removeMessages(UPDATE_VIDEO_CURRENT_POSITION);
}
};</span>
8.当对屏幕进行手势识别的时候,用GestureDetector,在onTouch事件时,交给GestureDetector的onTouchEvent来处理。
万能视频播放器
主要用开源的Vitamio来实现。
1.首先,在清单文件中加入
[java] view
plain copy
<span style="font-size:18px;"> <activity
android:name="io.vov.vitamio.activity.InitActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:launchMode="singleTop"
android:theme="@android:style/Theme.NoTitleBar"
android:windowSoftInputMode="stateAlwaysHidden" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</span>
2.在VideoPlayerActivity.java(视频播放页面)初始化数据时,加入
[java] view
plain copy
<span style="font-size:18px;"> // 初始化Vitamio SDK
if (!LibsChecker.checkVitamioLibs(this))
return;</span>
3.替换VideoView,成Vitamio的中的VideoView,在个类的路径
[java] view
plain copy
<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
<span style="font-size:18px;">//开启服务
startService(service);
//绑定服务,这样才能与其交互
bindService(service, conn, BIND_AUTO_CREATE);
</span>
第二步:
[java] view
plain copy
<span style="font-size:18px;"> //上面的开启服务方式,首先调用onStartCommand,然后调用onBind()
@Override
public IBinder onBind(Intent intent) {
return serviceMessenger.getBinder();
}
//在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
<span style="font-size:18px;">private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
}
};</span>
第三步:
[java] view
plain copy
<span style="font-size:18px;">//在Activty中,当service与Activty连接上时会回调此方法。
ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取service传递过来的信使
Messenger serviceMessenger = new Messenger(service);
//创建一条消息
Message message = Message.obtain(null, AudioPlayService.WHAT_UI_INTEFACE);
//携带UI类过去
message.obj = AudioPlayerActivity.this;
//告诉service,此UI的信使是哪个(这样,service就能拿到ui的信使,并用此信使,发送消息)
message.replyTo = uiMessenger;
//用service的信使,给service发送消息
try {
serviceMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};</span>
第四步:
[java] view
plain copy
<span style="font-size:18px;">//service中,处理自己的信使收到的消息
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case WHAT_UI_INTEFACE:
//拿到,传递过来的UI对象
audioUI = (AudioUI) msg.obj;
//获取ui对象的信使
Messenger uiMessenger = msg.replyTo;
//创建消息
Message message = Message.obtain(null, WHAT_PLAYSERVICE_INTERFACCE);
//把service传递过去
message.obj = AudioPlayService.this;
//
message.arg1 = flag ;
//用UI类的信使,发送消息
try {
uiMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msg);
}
};
</span>
第五步:
service中拿到了Activity中的信使,所以用此信使传递消息。
[java] view
plain copy
<span style="font-size:18px;">//这样在Ativity中又收到了service的消息,这样即确定了Service与Activity建立了可靠的连接。
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case AudioPlayService.WHAT_PLAYSERVICE_INTERFACCE:
//Activity 进行了三次握手,创建了连接
audioPlayService = (IPlayAudio) msg.obj;
if(msg.arg1 == -1){
//开启音频
audioPlayService.openAudio();
}else{
//从通知栏,点击进来额,机选刷新activity并不做其他处理
updateUI(audioPlayService.getCurrentMusicItem());
}
break;
case UPDATE_PLAY_TIME:
updatePlayTime();
break;
case UPDATE_LYRICS:
updateLyrics();
break;
}
super.handleMessage(msg);
}
};</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
相关文章推荐
- android 视频+音频播放器Demo
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)
- Android ijkplayer 强大的视频播放器框架教程 -- 导入demo运行(一)
- Android 本地视频播放器开发 —— ffmpeg解码视频文件中的音频
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- android之MediaPlayer播放音频或者视频文件
- android如何浏览并选择图片、音频 、视频
- android 编写简易视频播放器
- Android应用开发之视频播放器
- android-MediaPlayer的基本使用-播放音频视频
- 在Android中调用图片、视频、音频、录音、拍照
- 在Android 中调用选择图片、视频、添加音频、录音、拍摄视频、拍照等其他的功能
- 原创:Android应用开发记录-Andorid歌词秀(2)先来一个音频播放器
- Android 支持的媒体格式(音频,视频,图片)
- 免费Web视频及音频播放器
- android之视频播放器