在网上查了一下资料,感谢 http://www.cr173.com/html/20184_1.html 给了我思路,可以说他提供了最基本的歌词同步的功能,我在其上面添加了自己的修改的代码。
主要是自己为了实现歌词同步,并且通过移动seekbar,改变歌曲的歌词位置。当然还有自己不一样的地方。
首先歌词播放,是要一个子线程来操作,这个子线程负责在找到两段歌词之间的时间差,然后显示当前正在播放的歌词。
1
歌词部分
1.歌词的格式为.lrc 这是有一定格式的,最重要的是[MM:ss,mm]
以白玫瑰.lrc歌词为例
需要对歌词进行解析,歌词的实体类。
MyLrc.java
public class MyLrc implements Comparable<Object>{ private int time; private String lyric; public int getTime() { return time; } public void setTime(int time) { this.time = time; } public String getLyric() { return lyric; } public void setLyric(String lyric) { this.lyric = lyric; } //放在set集合中可以看下面要求进行排序 @Override public int compareTo(Object arg0) { int later=0; if(arg0 instanceof MyLrc) { later=((MyLrc)arg0).getTime(); } return this.time-later; } @Override public String toString() { return this.time+""+this.lyric; } }
实现comparaTo的方法的目的是在把对象放入TreeSet中的时候,按照歌词时间的循序放入,方便之后拿出来。比较使用(我觉得这部很重要,因为后期需要判断拉动SeekBar的时候找到对应的时间的位置)
LrcUtil.java
//对歌词进行解析 public class LrcUtil { private static TreeSet<MyLrc> tree; // 将对应的lrc文件转化为treeMap,分别对应的时间以及歌词 public LrcUtil(InputStream musicTitle) { TreeSet<MyLrc> treeset = new TreeSet<MyLrc>(); // 用来存放歌曲的时间和对应的歌词 InputStreamReader inReader = null; BufferedReader reader = null; try { inReader = new InputStreamReader(musicTitle); reader = new BufferedReader(inReader); String line = ""; while ((line = reader.readLine()) != null) { // 对那行歌词进行分割,判断,然后存储 String[] substr = line.split("\\]"); for (String ss : substr) { if (ss.contains("[") && ss.contains(":") && ss.contains(".")) { String sss = ss.replaceAll("\\[", ""); String[] timeStart = sss.split(":"); String[] timeEnd = timeStart[1].split("\\."); // 计算出当前的时间的毫秒数 int time = (Integer.valueOf(timeStart[0]) * 60 + Integer .valueOf(timeEnd[0])) * 1000 + Integer.valueOf(timeEnd[1]) * 10; // 对应的时间放一个对应的歌词 MyLrc lrc = new MyLrc(); lrc.setTime(time); lrc.setLyric(substr[substr.length - 1]); treeset.add(lrc); } } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { inReader.close(); reader.close(); } catch (IOException e) { e.printStackTrace(); } } tree = treeset; } //获得lrc文件的歌词点的列表 public List<String> getWords() { List<String> list = new ArrayList<String>(); Iterator<MyLrc> it = tree.iterator(); while (it.hasNext()) { MyLrc my = it.next(); list.add(my.getLyric()); } return list; } //获得lrc文件的时间点的列表 public List<Integer> getTimes() { List<Integer> list = new ArrayList<Integer>(); Iterator<MyLrc> it = tree.iterator(); while (it.hasNext()) { MyLrc my = it.next(); list.add(my.getTime()); } return list; } }
Lrc工具类在构造方法中传入一个InputStream流,由于现在是本地测试,所以本地先放入lrc文件。Android中可以把歌词文件放在Assets文件中,但是有些歌是中文的,而Assets文件中是不允许放中文的文件的
显示为非法名字,所以我打算把中文歌曲的名字转化为拼音再查找。
Activity中歌词显示的代码
根据当前歌曲的进度启一个子线程,sleep一段时间,其他时间刷新控件显示。
private void initLrcThread(int currentPosition) { LrcUtil lrcUtil = null; try { lrcUtil = new LrcUtil(getAssets().open(Trans2PinYin.trans2PinYin(currentMusic.getTitle())+".lrc")); playing_text_lrc.init(Trans2PinYin.trans2PinYin(currentMusic.getTitle())+".lrc"); } catch (IOException e) { ToastInfo(R.string.notfind_lrc); } if(lrcUtil==null) return; final List<Integer> lrctime = lrcUtil.getTimes(); if(currentPosition>lrctime.get(lrctime.size()-1)) return; int position = 0; for (int i = 0; i < lrctime.size()-1; i++) { if (currentPosition < lrctime.get(0)) { position = 0; break; } else if (currentPosition > lrctime.get(i) && currentPosition < lrctime.get(i + 1)) { position = i; break; } } final int p = position; //找到对应位置的歌词 playing_text_lrc.changeIndex(p); // 起一个子线程进行歌词显示 new Thread() { int i = p; public void run() { while (!mService.isPause()) { handler.post(new Runnable() { @Override public void run() { playing_text_lrc.invalidate(); } }); try { if (i == 0) Thread.sleep(lrctime.get(i)); else { Thread.sleep(lrctime.get(i + 1) - lrctime.get(i)); } } catch (InterruptedException e) { e.printStackTrace(); } i++; //防止下标越界! if (i+1 >= lrctime.size()) break; } ; } }.start(); }
这里是对自定义控件显示歌词的操作,如果没找到对应的lrc就打的Toast即可。
对于歌曲显示的子线程。当歌曲在播放的时候。线程sleep一段时间后对自定义的歌词控件进行刷新。每次刷新都会走空间中的ondraw的方法。
在ondraw方法中,刷新一次就跳到下一个歌词。
当然了,这里通过SeekBar移动的时候可以得到当前歌曲的位置(时间点),通过这个时间点(currentPosition)找到对应的getTimes列表中的歌词的位置
<span style="white-space:pre"> </span>for (int i = 0; i < lrctime.size()-1; i++) { <span style="white-space:pre"> </span>if (currentPosition < lrctime.get(0)) { <span style="white-space:pre"> </span>position = 0; <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>} else if (currentPosition > lrctime.get(i) <span style="white-space:pre"> </span>&& currentPosition < lrctime.get(i + 1)) { <span style="white-space:pre"> </span>position = i; <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>}并且同时改变
playing_text_lrc.changeIndex(p);
LRCTextView.java
public class LRCTextView extends TextView { private List<String> mWordsList = new ArrayList<String>(); private Paint mLoseFocusPaint; private Paint mOnFocusePaint; private float mX = 0; private float mMiddleY = 0; private float mY = 0; private static final int DY = 50; private int mIndex = 0; private Context context; public LRCTextView(Context context) throws IOException { super(context); this.context = context; } public LRCTextView(Context context, AttributeSet attrs) throws IOException { super(context, attrs); this.context = context; } public LRCTextView(Context context, AttributeSet attrs, int defStyle) throws IOException { super(context, attrs, defStyle); this.context = context; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint p = mLoseFocusPaint; // 未走到init未找到歌词 if (p == null) { p=new Paint(); p.setTextSize(50); canvas.drawText("未找到歌词", mX-100, mMiddleY, p); return; } p.setTextAlign(Paint.Align.CENTER); Paint p2 = mOnFocusePaint; p2.setTextAlign(Paint.Align.CENTER); // 防止下表越界 if (mIndex >= mWordsList.size()) return; canvas.drawText(mWordsList.get(mIndex), mX, mMiddleY, p2); int alphaValue = 25; float tempY = mMiddleY; for (int i = mIndex - 1; i >= 0; i--) { tempY -= DY; if (tempY < 0) { break; } p.setColor(Color.argb(255 - alphaValue, 245, 245, 245)); canvas.drawText(mWordsList.get(i), mX, tempY, p); alphaValue += 25; } alphaValue = 25; tempY = mMiddleY; for (int i = mIndex + 1, len = mWordsList.size(); i < len; i++) { tempY += DY; if (tempY > mY) { break; } p.setColor(Color.argb(255 - alphaValue, 245, 245, 245)); canvas.drawText(mWordsList.get(i), mX, tempY, p); alphaValue += 25; } mIndex++; } @Override protected void onSizeChanged(int w, int h, int ow, int oh) { super.onSizeChanged(w, h, ow, oh); mX = w * 0.5f; mY = h; mMiddleY = h * 0.3f; } public void init(String musicName) throws IOException { setFocusable(true); LrcUtil lrcHandler = new LrcUtil(context.getAssets().open(musicName)); mWordsList = lrcHandler.getWords(); mLoseFocusPaint = new Paint(); mLoseFocusPaint.setAntiAlias(true); mLoseFocusPaint.setTextSize(30); mLoseFocusPaint.setColor(Color.BLACK); mLoseFocusPaint.setTypeface(Typeface.SERIF); mOnFocusePaint = new Paint(); mOnFocusePaint.setAntiAlias(true); mOnFocusePaint.setColor(Color.RED); mOnFocusePaint.setTextSize(50); mOnFocusePaint.setTypeface(Typeface.SANS_SERIF); } public void changeIndex(int i) { mIndex = i; }这里我稍微修改了这个方法,传入一个changeIndex用于Seekbar被拉动时候,歌词的改变。其他的与http://www.cr173.com/html/20184_1.html相似
而在每次歌曲切换或者进度条移动的时候调用这个方法即可
initLrcThread(currentPosition);
这里用到了
Trans2PinYin.trans2PinYin(currentMusic.getTitle()
这个方法,这是中文转化为拼音的方法。百度即可。