android 視訊+音訊播放器Demo
程式主介面
- MainActivity.java
1.主介面,頭部是兩個TextView(自定義類似指標效果),底部是ViewPager。ViewPager中每個頁面對應的是一個Fragment.這樣就搭起了首頁。
xml檔案程式碼:
2.當選中音訊或者視訊時,TextView的文字顏色和大小都改變。<?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>
3.滑動指示先與ViewPager的結合。/** * 選擇:音訊 / 音樂 * @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); }
<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
fragmentAdapter.addItem(new VideoFragment());
fragmentAdapter.addItem(new MusicFragment());
- VideoFragment.java(視訊模組)
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都會變化。
<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>
音訊和視訊的區別就在於這些引數上<span style="font-size:18px;"> task.startQuery(0,null, getUri(), getProjection(),null,null,getOrderBy());</span>
所以,我們抽取成abstract方法,讓具體的子類來實現。
音訊的地址:
<span style="font-size:18px;">MediaStore.Audio.Media.EXTERNAL_CONTENT_URI</span>
視訊的地址:
<span style="font-size:18px;">MediaStore.Video.Media.EXTERNAL_CONTENT_URI</span>
5.查詢成功後,會返回一個cursor,其中包括了資料,將資料設定到adapter中,而且這個adapter需要繼承自CursorAdapter<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來進行播放視訊。
主要涉及到了一下幾個方法。
<span style="font-size:18px;">//1.設定播放路徑
mVideoView.setVideoPath(item.getPath());
</span>
<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.獲取視訊的播放時長和當前時長
<span style="font-size:18px;">mVideoView.getDuration()</span>
<span style="font-size:18px;">mVideoView.getCurrentPosition()</span>
5.設定視訊播放前的監聽<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.設定視訊快取(針對網路視訊,這個方法是用來更新第二進度條的)<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.設定視訊播放完的監聽<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來處理。
- 萬能視訊播放器
1.首先,在清單檔案中加入
<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(視訊播放頁面)初始化資料時,加入<span style="font-size:18px;"> // 初始化Vitamio SDK
if (!LibsChecker.checkVitamioLibs(this))
return;</span>
3.替換VideoView,成Vitamio的中的VideoView,在個類的路徑<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此握手,才能建立連線、
第一步:
<span style="font-size:18px;">//開啟服務
startService(service);
//繫結服務,這樣才能與其互動
bindService(service, conn, BIND_AUTO_CREATE);
</span>
第二步:<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,用來處理此信使獲取到的資訊<span style="font-size:18px;">private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
}
};</span>
第三步:
<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>
第四步:<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中的信使,所以用此信使傳遞訊息。
<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