您的位置:首页 > 移动开发 > Android开发

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: