安卓MP3播放器开发实例(3)之进度条和歌词更新的实现
? ? ?? ?上一次談了音樂播放的實現,這次說下最復雜的進度條和歌詞更新。因為須要在播放的Activity和播放的Service間進行交互,所以就涉及了Activity對Service的綁定以及綁定后數據的傳輸,這個須要對服務綁定熟悉才干夠理解。原理不復雜。可是步驟略微繁瑣,代碼貼起來可能會非常混亂。
? ? ? 進度條和歌詞放在一起說比較好,不然比較混亂。進度條的調整大家都懂的,就是進度條調到哪里歌曲的播放就跟到哪里,歌詞也得跟到哪里。首先看下上一篇看過的開始button監聽事件中服務的綁定代碼:
? ? ??
//綁定播放服務bindService(intent, conn,BIND_AUTO_CREATE); //Runnable對象增加消息隊列,在handler綁定的線程中運行,用于歌詞更新handler.post(updateTimeCallback);start = System.currentTimeMillis();??bindService(intent, conn,BIND_AUTO_CREATE); 中的conn是綁定服務后的一個回調接口。我們看下代碼:? ? ??
/*** 傳入bindService()中的回調接口*/ServiceConnection conn = new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName arg0, IBinder binder) {//綁定成功后調用該方法PlayActivity.this.binder = (Binder)binder;}@Overridepublic void onServiceDisconnected(ComponentName arg0) {}};事實上就是一個語句。作用就是將播放服務返回的IBinder對象引用賦給播放Activity,這樣Activity就能夠獲取Service返回的數據了,我們這里是為了獲取此時播放的進度數據。? ? ?? handler.post(updateTimeCallback);是將一個Runnable對象增加隊列中。這個Runnable對象updateTimeCallback
? 就是實現進度條和歌詞更新的核心實現代碼,只是看這個類之前。我們先看下進度條的監聽事件:
? ?
/*** 經過試驗假設把進度條調動之前和調動之后合并為一種方式實現歌詞的更新的話會奔潰,原因可能是不同線程間資源的同步問題,僅僅好拆成* 進度條調動之前(change == false)和調動之后(change == true)兩部分在歌詞更新的線程中運行* @author yan**/class SeekBarListener implements OnSeekBarChangeListener{ @Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}@Overridepublic void onStartTrackingTouch(SeekBar arg0) {Log.d("yinan", "start--------"+seekBar.getProgress());}@Overridepublic void onStopTrackingTouch(SeekBar arg0) {if(stopMusic == false){change = true;//進度條進度人為改變。將改變后的進度發送給播放服務Intent intent = new Intent();intent.setClass(PlayActivity.this, PlayService.class);intent.putExtra("progress", seekBar.getProgress());startService(intent);}else{seekBar.setProgress(0);}}} 在進度條位置得到調整之后,仍然用startService(intent)方式。將封裝進度條進度的Intent對象傳給Service的onStartCommand方法處理。? ? ? ? 如今來看看實現的核心代碼Runnable對象updateTimeCallback的類:
? ?
/*** 異步調整進度條位置與歌曲進度一致和顯示歌詞的類* @author yinan**/class UpdateTimeCallback implements Runnable{Queue times = null;Queue messages = null;ArrayList<Queue> queues = null;public UpdateTimeCallback(ArrayList<Queue> queues){times = queues.get(0);messages = queues.get(1);this.queues = queues;}/*** run方法因為沒有條用start所以并沒有開辟子線程。所以在內部開啟子線程*/@Overridepublic void run() {total = PlayService.totalLength;if(change == true){Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain();data.writeString("change");try {binder.transact(0, data, reply, 0);} catch (RemoteException e) {e.printStackTrace();}//直到Service返回才運行這一句float s = reply.readFloat();//s為歌曲播放的時間進度float f = s*100;seekBar.setProgress((int)f);//轉化為進度條進度//此時相應的播放時間點final long t = (long) (s*total);preparelrc(mp3Info.getLrcName());times = queues.get(0);messages = queues.get(1);System.out.println("times"+times.size());System.out.println("messages"+messages.size());if(stopMusic == false){new Thread(){public void run(){while(nextTime < t){ //從頭遍歷歌詞,時間隊列直到時間點大于當前時間System.out.println("nextTime"+nextTime);///lyric = message;if((times.size()>0)||messages.size()>0){nextTime = (Long)times.poll();message = (String)messages.poll(); }else{lyric = null;stopMusic = true;}//保存時間點剛大于當前時間的上一條歌詞,即最接近當前的歌詞System.out.println("nextTime"+nextTime);}}}.start();System.out.println("lyric"+lyric);if(lyric != null){lrcText.setText(lyric);}} }if(!change){nowTime = System.currentTimeMillis() - start;seekBar.setProgress((int)(nowTime*100/total));if(stopMusic == false){new Thread(){public void run(){while(nextTime < nowTime){ //從頭遍歷歌詞。時間隊列直到時間點小于當前時間System.out.println("nextTime"+nextTime);lyric = message;//保存時間點剛大于當前時間的上一條歌詞,即最接近當前的歌詞if((times.size()>0)||messages.size()>0){nextTime = (Long)times.poll();message = (String)messages.poll(); }else{lyric = null;stopMusic = true;}}}}.start();if(lyric != null){lrcText.setText(lyric);} }}if(stopMusic == true){handler.removeCallbacks(updateTimeCallback);}handler.postDelayed( updateTimeCallback, 200);//每20毫秒運行一次線程}} 這段代碼大致是當獲取到服務返回的歌曲實時進度的數據時,將進度條的位置進行調整到相應為孩子的處理,然后將歌詞更新到相應的部分。因為進度條要實時跟進。全部UpdateTimeCallback類的run方法是每隔200毫秒就運行一次,到Service中獲取返回值。這里是將處理的情況分為進度條被改變了和未被改變兩種情況,用標志位change表示(一旦進度條被改變了就運行了change為true那一段代碼)。
開子線程是由于對歌詞的處理耗時。不適合放在UI線程中。
?? 詳細的流程是:
? 首先,Parcel data = Parcel.obtain(); ?
? ?Parcel reply = Parcel.obtain();
? ?data.writeString("change");
? ?binder.transact(0, data, reply, 0);
? ? ? binder就是之前和Service綁定的IBinder對象,binder.transact是將一個Parcel對象(傳入"change"不過一個標志)傳給Service,然后Service返回歌曲進度,在float s = reply.readFloat()這句中接收,s是一個當前進度占總進度的百分比數。然后將進度條位置設置一下就可以。
然后進行歌詞更新的處理。
首先是對歌詞的準備處理工作,preparelrc(mp3Info.getLrcName()),將lrc格式的歌詞中的時間點和相應的詳細歌詞拆分成兩個隊列。(詳細代碼請看源代碼)然后依據Service返回的歌曲播放進度。對兩個隊列進行遍歷。當有時間點超過進度相應的時間點時,將該時間點相應的歌詞取出,顯示在Activity上。
前面總是說Service返回歌曲實時進度,我們來看看Service中對于數據返回的處理。
? ? ?當進度條被調整時。會進入Service的onStartCommand方法,進入到下面的if語句中:
? ??
if(intent.getIntExtra("progress", 0) != 0){///if(isPlay){//進度條進度int progress = intent.getIntExtra("progress", 0);if(progress != 0)//將音樂進度調整到相應位置mediaPlayer.seekTo(progress*totalLength/100);}}Service又是怎樣實時返回歌曲進度的呢?Service中有一個Binder類。是IBinder的子類:/**運行 binder.transact()后運行*/class FirstBinder extends Binder{ @Overrideprotected boolean onTransact(int code, Parcel data, Parcel reply,int flags) throws RemoteException {String str = data.readString();int currentPosition = mediaPlayer.getCurrentPosition();float s = (float)currentPosition/mediaPlayer.getDuration();if(isPlay)reply.writeFloat(s);return super.onTransact(code, data, reply, flags);} 是的。它就是之前Activity與Service綁定后獲得的IBinder對象。每當Activity的binder.transact(0, data, reply, 0);被調用的時候。Service就會調用onTransact方法。將獲取到的數據裝入Parcel對象reply中,然后將整個IBinder對象返回Activity。
MP3播放器就這樣講完了,可能講的非常亂。希望對各位有幫助。源碼下載鏈接:http://download.csdn.net/detail/sinat_23092639/8933995
轉載于:https://www.cnblogs.com/gavanwanggw/p/6958271.html
總結
以上是生活随笔為你收集整理的安卓MP3播放器开发实例(3)之进度条和歌词更新的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: resourceAsStream
- 下一篇: 【CSS3】自定义滚动条样式 -webk