package com.lq.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.annotation.SuppressLint; import android.util.Log; import com.lq.entity.LyricSentence; /** * @author lq 2013-6-1 lq2625304@gmail.com * */ public class LyricLoadHelper { /** 用于向外通知歌词载入、变化的监听器 */ public interface LyricListener { /** * 歌词载入时调用 * * @param lyricSentences * 歌词文本处理后的所有歌词句子 * @param indexOfCurSentence * 正在播放的句子在句子集合中的索引号 */ public abstract void onLyricLoaded(List<LyricSentence> lyricSentences, int indexOfCurSentence); /** * 歌词变化时调用 * * @param indexOfCurSentence * 正在播放的句子在句子集合中的索引号 * @param currentTime * 已经播放的毫秒数 * */ public abstract void onLyricSentenceChanged(int indexOfCurSentence); } private static final String TAG = LyricLoadHelper.class.getSimpleName(); /** 句子集合 */ private ArrayList<LyricSentence> mLyricSentences = new ArrayList<LyricSentence>(); private LyricListener mLyricListener = null; private boolean mHasLyric = false; /** 当前正在播放的歌词句子的在句子集合中的索引号 */ private int mIndexOfCurrentSentence = -1; /** 用于缓存的一个正则表达式对象,识别[]中的内容,不包括中括号 */ private final Pattern mBracketPattern = Pattern .compile("(?<=\\[).*?(?=\\])"); private final Pattern mTimePattern = Pattern .compile("(?<=\\[)(\\d{2}:\\d{2}\\.?\\d{0,3})(?=\\])"); private final String mEncoding = "utf-8"; public List<LyricSentence> getLyricSentences() { return mLyricSentences; } public void setLyricListener(LyricListener listener) { this.mLyricListener = listener; } public void setIndexOfCurrentSentence(int index) { mIndexOfCurrentSentence = index; } public int getIndexOfCurrentSentence() { return mIndexOfCurrentSentence; } /** * 根据歌词文件的路径,读取出歌词文本并解析 * * @param lyricPath * 歌词文件路径 * @return true表示存在歌词,false表示不存在歌词 */ public boolean loadLyric(String lyricPath) { Log.i(TAG, "LoadLyric begin,path is:" + lyricPath); mHasLyric = false; mLyricSentences.clear(); if (lyricPath != null) { File file = new File(lyricPath); if (file.exists()) { Log.i(TAG, "歌词文件存在"); mHasLyric = true; try { FileInputStream fr = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fr, mEncoding); BufferedReader br = new BufferedReader(isr); String line = null; // 逐行分析歌词文本 while ((line = br.readLine()) != null) { Log.i(TAG, "lyric line:" + line); parseLine(line); } // 按时间排序句子集合 Collections.sort(mLyricSentences, new Comparator<LyricSentence>() { // 内嵌,匿名的compare类 public int compare(LyricSentence object1, LyricSentence object2) { if (object1.getStartTime() > object2 .getStartTime()) { return 1; } else if (object1.getStartTime() < object2 .getStartTime()) { return -1; } else { return 0; } } }); for (int i = 0; i < mLyricSentences.size() - 1; i++) { mLyricSentences.get(i).setDuringTime( mLyricSentences.get(i + 1).getStartTime()); } mLyricSentences.get(mLyricSentences.size() - 1) .setDuringTime(Integer.MAX_VALUE); fr.close(); } catch (Exception e) { e.printStackTrace(); } finally { } } else { Log.i(TAG, "歌词文件不存在"); } } // 如果有谁在监听,通知它歌词载入完啦,并把载入的句子集合也传递过去 if (mLyricListener != null) { mLyricListener.onLyricLoaded(mLyricSentences, mIndexOfCurrentSentence); } if (mHasLyric) { Log.i(TAG, "Lyric file existed.Lyric has " + mLyricSentences.size() + " Sentences"); } else { Log.i(TAG, "Lyric file does not existed"); } return mHasLyric; } /** * 根据传递过来的已播放的毫秒数,计算应当对应到句子集合中的哪一句,再通知监听者播放到的位置。 * * @param millisecond * 已播放的毫秒数 */ public void notifyTime(long millisecond) { // Log.i(TAG, "notifyTime"); if (mHasLyric && mLyricSentences != null && mLyricSentences.size() != 0) { int newLyricIndex = seekSentenceIndex(millisecond); if (newLyricIndex != -1 && newLyricIndex != mIndexOfCurrentSentence) {// 如果找到的歌词和现在的不是一句。 if (mLyricListener != null) { // 告诉一声,歌词已经变成另外一句啦! mLyricListener.onLyricSentenceChanged(newLyricIndex); } mIndexOfCurrentSentence = newLyricIndex; } } } private int seekSentenceIndex(long millisecond) { int findStart = 0; if (mIndexOfCurrentSentence >= 0) { // 如果已经指定了歌词,则现在位置开始 findStart = mIndexOfCurrentSentence; } try { long lyricTime = mLyricSentences.get(findStart).getStartTime(); if (millisecond > lyricTime) { // 如果想要查找的时间在现在字幕的时间之后 // 如果开始位置经是最后一句了,直接返回最后一句。 if (findStart == (mLyricSentences.size() - 1)) { return findStart; } int new_index = findStart + 1; // 找到第一句开始时间大于输入时间的歌词 while (new_index < mLyricSentences.size() && mLyricSentences.get(new_index).getStartTime() <= millisecond) { ++new_index; } // 这句歌词的前一句就是我们要找的了。 return new_index - 1; } else if (millisecond < lyricTime) { // 如果想要查找的时间在现在字幕的时间之前 // 如果开始位置经是第一句了,直接返回第一句。 if (findStart == 0) return 0; int new_index = findStart - 1; // 找到开始时间小于输入时间的歌词 while (new_index > 0 && mLyricSentences.get(new_index).getStartTime() > millisecond) { --new_index; } // 就是它了。 return new_index; } else { // 不用找了 return findStart; } } catch (IndexOutOfBoundsException e) { e.printStackTrace(); Log.i(TAG, "新的歌词载入了,所以产生了越界错误,不用理会,返回0"); return 0; } } /** 解析每行歌词文本,一行文本歌词可能对应多个时间戳 */ private void parseLine(String line) { if (line.equals("")) { return; } String content = null; int timeLength = 0; int index = 0; Matcher matcher = mTimePattern.matcher(line); int lastIndex = -1;// 最后一个时间标签的下标 int lastLength = -1;// 最后一个时间标签的长度 // 一行文本歌词可能对应多个时间戳,如“[01:02.3][01:11:22.33]在这阳光明媚的春天里” // 一行也可能包含多个句子,如“[01:02.3]在这阳光明媚的春天里[01:02:22.33]我的眼泪忍不住流淌” List<String> times = new ArrayList<String>(); // 寻找出本行所有时间戳,存入times中 while (matcher.find()) { // 匹配的是中括号里的字符串,如01:02.3,01:11:22.33 String s = matcher.group(); index = line.indexOf("[" + s + "]"); if (lastIndex != -1 && index - lastIndex > lastLength + 2) { // 如果大于上次的大小,则中间夹了别的内容在里面 // 这个时候就要分段了 content = trimBracket(line.substring( lastIndex + lastLength + 2, index)); for (String string : times) { // 将每个时间戳对应的一份句子存入句子集合 long t = parseTime(string); if (t != -1) { Log.i(TAG, "line content match-->" + content); mLyricSentences.add(new LyricSentence(t, content)); } } times.clear(); } times.add(s); lastIndex = index; lastLength = s.length(); Log.i(TAG, "time match--->" + s); } // 如果列表为空,则表示本行没有分析出任何标签 if (times.isEmpty()) { return; } timeLength = lastLength + 2 + lastIndex; if (timeLength > line.length()) { content = trimBracket(line.substring(line.length())); } else { content = trimBracket(line.substring(timeLength)); } Log.i(TAG, "line content match-->" + content); // 将每个时间戳对应的一份句子存入句子集合 for (String s : times) { long t = parseTime(s); if (t != -1) { mLyricSentences.add(new LyricSentence(t, content)); } } } /** 去除指定字符串中包含[XXX]形式的字符串 */ private String trimBracket(String content) { String s = null; String result = content; Matcher matcher = mBracketPattern.matcher(content); while (matcher.find()) { s = matcher.group(); result = result.replace("[" + s + "]", ""); } return result; } /** 将歌词的时间字符串转化成毫秒数,如果参数是00:01:23.45 */ @SuppressLint("DefaultLocale") private long parseTime(String strTime) { String beforeDot = new String("00:00:00"); String afterDot = new String("0"); // 将字符串按小数点拆分成整秒部分和小数部分。 int dotIndex = strTime.indexOf("."); if (dotIndex < 0) { beforeDot = strTime; } else if (dotIndex == 0) { afterDot = strTime.substring(1); } else { beforeDot = strTime.substring(0, dotIndex);// 00:01:23 afterDot = strTime.substring(dotIndex + 1); // 45 } long intSeconds = 0; int counter = 0; while (beforeDot.length() > 0) { int colonPos = beforeDot.indexOf(":"); try { if (colonPos > 0) {// 找到冒号了。 intSeconds *= 60; intSeconds += Integer.valueOf(beforeDot.substring(0, colonPos)); beforeDot = beforeDot.substring(colonPos + 1); } else if (colonPos < 0) {// 没找到,剩下都当一个数处理了。 intSeconds *= 60; intSeconds += Integer.valueOf(beforeDot); beforeDot = ""; } else {// 第一个就是冒号,不可能! return -1; } } catch (NumberFormatException e) { return -1; } ++counter; if (counter > 3) {// 不会超过小时,分,秒吧。 return -1; } } // intSeconds=83 String totalTime = String.format("%d.%s", intSeconds, afterDot);// totaoTimer // = // "83.45" Double doubleSeconds = Double.valueOf(totalTime); // 转成小数83.45 return (long) (doubleSeconds * 1000);// 转成毫秒8345 } }