Android歌詞播放的實現
阿新 • • 發佈:2018-12-30
公司專案最近有需求,要實現一個音樂系統,涉及到一個歌詞播放的功能,現將這個實現過程寫下來。
做之前先上網查了下相關APP的情況,發現QQ音樂的桌面歌詞效果,正是我想要的,顯示兩行歌詞,輪流播放,並顯示播放時的過渡效果。
首先分析的出,歌詞有兩行,那麼應該是兩個控制元件來的分別顯示,然後通過一系列演算法來控制兩行歌詞交替顯示,這樣的話,我們要先寫出用於顯示歌詞的控制元件來。
歌詞顯示自定義控制元件
歌詞控制元件應該具備的方法應該有,設定歌詞,設定字型顏色,字型大小,播放方法。
播放方法的引數一定要有播放的時長,和一個播放結束完畢的監聽,這個監聽提供給呼叫者,這樣才可以播放下一句歌詞。
我先將整個播放程式碼貼上來:
/** * 每執行一次,都會降計時器清零 * * @param delay 執行前等待時間 * @param period 迴圈時間 */ public void showLrc(final Activity context, long delay, long period, final ShowListener listener) { tvSelect.setVisibility(VISIBLE); tvSelect.setWidth(0); tvDefault.setText(lrc); tvSelect.setText(lrc); Log.i("_lrc", "start_openCounter:" + mOpenCounter.get()); mOpenCounter.addAndGet(1); if (period < 100) period = 100; float speed = period / 100; mTimer = new Timer(); TimerTask task = new TimerTask() { public void run() { context.runOnUiThread(new Runnable() { @Override public void run() { if (percent < 100) { percent++; } else { Log.i("_lrc", "end_openCounter:" + mOpenCounter.get()); if (mOpenCounter.get() > 0) { mOpenCounter.set(0); percent = 0; listener.showFinish(); tvSelect.setVisibility(INVISIBLE); cancel(); } } setPercent(); } }); } }; mTimer.schedule(task, delay, (long) speed); }
程式碼上有我剛才提到的幾個方法,比如設定歌詞,設定字型大小,播放等方法,但仔細看下歌詞的引數,仔細看第二個引數,這個引數是用於播放前等待時間,因為歌手在唱下一句的時候,會停留一些時間,會喘口氣,休息一會。
歌詞要播放的時候顯示過渡效果的話,就還要做一些處理,這裡的實現原理是,用兩個不同顏色的TextView重疊著,我們動態設定上面的TextView的寬度,就可以實現我們要的過渡效果了。
解析歌詞工具類
歌詞顯示的控制元件有了,接下來我們寫一個解析歌詞的工具類,這個類的作用是傳入一個LRC歌詞路徑,然後將歌詞和歌詞對應時間用兩個陣列容器存放著,提供給呼叫者獲取出歌詞和對應的時間即可。 LRC歌詞是歌詞前面會有對應的播放時間,這個時間用中括號括著,我們通過一點點解析演算法,將其放到兩個容器就好了。package com.ysbing.lrcshow; import android.text.TextUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class LrcUtil { private List<Integer> mTimeList = new ArrayList<>(); private List<String> mWords = new ArrayList<>(); private Iterator<Integer> mTimeIterator; private Iterator<String> mWordIterator; public LrcUtil(File lrcFile) throws FileNotFoundException { readLrcFile(lrcFile); } public boolean hasTime() { return mTimeIterator.hasNext(); } public boolean hasWord() { return mWordIterator.hasNext(); } public int getTime() { return mTimeIterator.next(); } public String getWord() { return mWordIterator.next(); } //處理歌詞檔案 private void readLrcFile(File file) throws FileNotFoundException { FileInputStream fileInputStream = new FileInputStream(file); InputStreamReader inputStreamReader; try { inputStreamReader = new InputStreamReader( fileInputStream, "utf-8"); readLrcStream(inputStreamReader); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } private void readLrcStream(InputStreamReader inputStreamReader) { BufferedReader bufferedReader = new BufferedReader( inputStreamReader); String s; try { while ((s = bufferedReader.readLine()) != null) { addTimeToList(s); if ((s.contains("[ar:")) || (s.contains("[ti:")) || (s.contains("[by:")) || (s.contains("[al:"))) { // s = s.substring(s.indexOf(":") + 1, s.indexOf("]")); continue; } else { if (TextUtils.isEmpty(s)) continue; int startIndex = s.indexOf("["); int endIndex = s.indexOf("]"); if (startIndex >= 0 && endIndex >= 0) { String ss = s.substring(startIndex, endIndex + 1); s = s.replace(ss, ""); } else continue; } mWords.add(s); } mTimeIterator = mTimeList.iterator(); mWordIterator = mWords.iterator(); bufferedReader.close(); inputStreamReader.close(); } catch (IOException e) { e.printStackTrace(); mWords.add("沒有讀取到歌詞"); } } private void addTimeToList(String string) { Matcher matcher = Pattern.compile( "\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string); if (matcher.find()) { String str = matcher.group(); mTimeList.add(timeHandler(str.substring(1, str.length() - 1))); } } // 分離出時間 private int timeHandler(String string) { string = string.replace(".", ":"); String timeData[] = string.split(":"); // 分離出分、秒並轉換為整型 int minute = Integer.parseInt(timeData[0]); int second = Integer.parseInt(timeData[1]); int millisecond = Integer.parseInt(timeData[2]); if (timeData[2].length() == 1) millisecond *= 100; else if (timeData[2].length() == 2) millisecond *= 10; // 計算上一行與下一行的時間轉換為毫秒數 return (minute * 60 + second) * 1000 + millisecond; } }
歌詞控制
歌詞解析工具也有了,接下來要做的事情,就是本節部落格的核心,分析如何控制兩個歌詞的交替顯示。 歌詞有兩行,我們先開始命名,第一行歌詞的時間叫“時間一”,歌詞叫“歌詞一”,第二行則叫“時間二”和“歌詞二”。 如果歌詞1為空,設定歌詞1為下一句歌詞,記錄時間1-時間2為歌詞2的等待時間歌詞1播放->時間2-時間1
我們要考慮到的情況有,在LRC歌詞檔案中,在歌手喘氣休息的時候,是沒歌詞的,這種情況在LRC中,沒歌詞,有時間; 假設我們在播放第一行歌詞的時候,就有這樣的邏輯
如果歌詞二為空,就獲取下一句歌詞作為歌詞二,獲取下一個時間為時間二
在歌詞一播放完後,我們在播放完的回撥中,再播放歌詞二,這樣迴圈下去,知道歌詞結束。
這樣我們就實現了歌詞的交叉播放,但還存在一個問題,就是,播放的時候是一句接著一句的,如果手機效能比較差,就可能慢慢的,就會和音樂不同了,這個情況也是不允許出現的,所以開始升級。
升級後邏輯是:
記錄開始時間
播放前獲取現在時間,減去和歌詞時間的差
歌詞結束時間減去開始播放時間,算出速度
這樣總體我們實現完了,效果還可以,就算手機再差,也會在播放下一句中的時候,準確對準歌詞的時間。原始碼我上傳到CSDN,再去下載看看具體邏輯吧,謝謝觀看!
資源下載地址:http://download.csdn.net/detail/ysb794008002/9586853