android 音乐播放器关于歌词的处理
2016-05-08 13:06
423 查看
当我们制作音乐播放器中我觉得歌词的处理是比较难的一块, 对于音乐播放和媒体控制我们可以使用MediaPlayer来搞定,它提供了媒体控制的接口,使得我们对于媒体控制来说变得比较简单。但对于显示歌词来说就比较复杂了一点,例如让歌词一个字一个字高亮、快进时控制歌词处理或者倍速播放时歌词的处理等等, 这里我想介绍简单让一行歌词高亮显示, 等这行歌词唱完,让下一行歌词高亮显示。
1. 解析歌词文件
常见的歌词文件有:.lrc 和 .txt格式, 内容格式为:[00:02.59] 飘洋过海来看你
所以我们先要去解析歌词文件, 定义一个类去保存每行解析出来的数据。
现在我们开始解析歌词文件然后将数据保存到Lyrc中
我们可以直接使用LrcUtils类, 导入到你的工程中,调用LrcUtils.readLRC(File);方法传入歌词文件就会返回解析的歌词数据.
下面介绍LrcUtils类中的逻辑:
(1)首先从歌词文件中读取数据(按行读取, 在读取数据时调用方法getCharset获取文件的编码格式防止读取数据出现乱码), 每读取一行调用processLRC去将此行数据解析保存到Lyrc中.
(2)在processLRC中处理一行中多个时间戳的逻辑, 因为有的歌词文件为了方便把重复的歌词的时间放在一起。
(3)将获取的解析数据按照timePoint排序, 因为防止(2)中描述的情况,重复歌词的时间放在一起的问题,如果不排序的话,会导致歌词顺序乱套.
(4)计算每行歌词停留的时间
什么是为了防止重复歌词放在一行上?
歌词是这样的:
一句歌词前面多个时间tag.
2. 编写自定义TextView 去显示歌词
(1)继承TextView, 处理onDraw方法
(2)歌词绘制,区分当前行与普通行, 将当前行绘制在控件中心。指定两种Paint,来绘制两种不同文本。
(3)每隔一个时间段(就是Lyrc中的sleepTime字段)更新显示内容,向上滚动
在自定义LricView中调用LrcUtils.readLRC方法传入歌词文件获取歌词信息,然后通过handler去控制多长时间进行绘制. LricView 可以直接使用.
3. 使用LricView
在布局文件中直接使用即可.
最终实现结果:
自此歌词处理完成, 贴出的代码都是可以直接使用参考, 关于快进快退音乐和倍速播放,歌词的处理逻辑就是修改lyric中的SleepTime, 不过逻辑也挺绕的. 这个歌词处理逻辑是我在做音乐播放器时参考学习别的教程, 在这里整理出来给大家参考, 如果有任何问题可以留言.
1. 解析歌词文件
常见的歌词文件有:.lrc 和 .txt格式, 内容格式为:[00:02.59] 飘洋过海来看你
所以我们先要去解析歌词文件, 定义一个类去保存每行解析出来的数据。
public class Lyric { public String lricString; public int sleepTime; public int timePoint; }我们对照着 "[00:02.59] 飘洋过海来看你" 来看, lrcString保存的是"飘洋过海来看你", sleepTime保存的是这句歌词播放时间, 就是下一句歌词开始播的时间减去本句歌词播放的时间,timePoint就是将 “[00:02.59]” 时间文本解析出来转换成的秒数。
现在我们开始解析歌词文件然后将数据保存到Lyrc中
public class LrcUtils { private static List<Lyric> lyricList; /** * 读取文件 */ public static List<Lyric> readLRC(File f) { try { if (f == null || !f.exists()) { lyricList = null; } else { lyricList = new Vector<Lyric>(); InputStream is = new BufferedInputStream(new FileInputStream(f)); BufferedReader br = new BufferedReader(new InputStreamReader( is, getCharset(f))); String strTemp = ""; while ((strTemp = br.readLine()) != null) { strTemp = <span style="font-family:Arial, Helvetica, sans-serif;font-size:10px;">processLRC</span>(strTemp); } br.close(); is.close(); // 对歌词进行排序 Collections.sort(lyricList, new Sort()); // 计算每行歌词的停留时间 for (int i = 0; i < lyricList.size(); i++) { Lyrc one = lyricList.get(i); if (i + 1 < lyricList.size()) { Lyric two = lyricList.get(i + 1); one.sleepTime = two.timePoint - one.timePoint; } } } } catch (Exception e) { e.printStackTrace(); } return lyricList; } /** * 处理一行内容 */ private static String processLRC(String text) { try { int pos1 = text.indexOf("["); int pos2 = text.indexOf("]"); if (pos1 >= 0 && pos2 != -1) { Long time[] = new Long[getPossiblyTagCount(text)]; time[0] = timeToLong(text.substring(pos1 + 1, pos2)); if (time[0] == -1) return ""; String strLineRemaining = text; int i = 1; while (pos1 >= 0 && pos2 != -1) { strLineRemaining = strLineRemaining.substring(pos2 + 1); pos1 = strLineRemaining.indexOf("["); pos2 = strLineRemaining.indexOf("]"); if (pos2 != -1) { time[i] = timeToLong(strLineRemaining.substring( pos1 + 1, pos2)); if (time[i] == -1) return ""; // LRCText i++; } } Lyric tl = null; //防止有的歌词文件是这种格式:[00:01:23][00:03:02]重复歌词 //就是歌词重复的放在一起,将多个时间戳放在一起,所以在解析完歌词需要排序一下。 for (int j = 0; j < time.length; j++) { if (time[j] != null) { tl = new Lyric(); tl.timePoint = time[j].intValue(); tl.lricString = strLineRemaining; lyrcList.add(tl); } } return strLineRemaining; } else return ""; } catch (Exception e) { return ""; } } //获取一行中的时间标签的个数,为了防止将重复歌词放在一行上显示 private static int getPossiblyTagCount(String Line) { String strCount1[] = Line.split("\\["); String strCount2[] = Line.split("\\]"); if (strCount1.length == 0 && strCount2.length == 0) return 1; else if (strCount1.length > strCount2.length) return strCount1.length; else return strCount2.length; } /** * 时间转换,将time格式时间转换成秒 */ public static long timeToLong(String Time) { try { String[] s1 = Time.split(":"); int min = Integer.parseInt(s1[0]); String[] s2 = s1[1].split("\\."); int sec = Integer.parseInt(s2[0]); int mill = 0; if (s2.length > 1) mill = Integer.parseInt(s2[1]); return min * 60 * 1000 + sec * 1000 + mill * 10; } catch (Exception e) { return -1; } } /** * 判断文件编码,防止文件解析成乱码 */ public static String getCharset(File file) { String charset = "GBK"; byte[] first3Bytes = new byte[3]; try { boolean checked = false; BufferedInputStream bis = new BufferedInputStream( new FileInputStream(file)); bis.mark(0); //一般读取前3个字节就可以判断文件的编码格式 int read = bis.read(first3Bytes, 0, 3); if (read == -1) return charset; if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) { charset = "UTF-16LE"; checked = true; } else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) { charset = "UTF-16BE"; checked = true; } else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB && first3Bytes[2] == (byte) 0xBF) { charset = "UTF-8"; checked = true; } bis.reset(); if (!checked) { int loc = 0; while ((read = bis.read()) != -1) { loc++; if (read >= 0xF0) break; if (0x80 <= read && read <= 0xBF) break; if (0xC0 <= read && read <= 0xDF) { read = bis.read(); if (0x80 <= read && read <= 0xBF) continue; else break; } else if (0xE0 <= read && read <= 0xEF) { read = bis.read(); if (0x80 <= read && read <= 0xBF) { read = bis.read(); if (0x80 <= read && read <= 0xBF) { charset = "UTF-8"; break; } else break; } else break; } } } bis.close(); } catch (Exception e) { e.printStackTrace(); } return charset; } //按照timePoint的大小进行升序排列 private static class Sort implements Comparator<Lyrc> { public Sort() { } public int compare(Lyric tl1, Lyric tl2) { return sortUp(tl1, tl2); } private int sortUp(Lyric tl1, Lyric tl2) { if (tl1.timePoint < tl2.timePoint) return -1; else if (tl1.timePoint > tl2.timePoint) return 1; else return 0; } } }
我们可以直接使用LrcUtils类, 导入到你的工程中,调用LrcUtils.readLRC(File);方法传入歌词文件就会返回解析的歌词数据.
下面介绍LrcUtils类中的逻辑:
(1)首先从歌词文件中读取数据(按行读取, 在读取数据时调用方法getCharset获取文件的编码格式防止读取数据出现乱码), 每读取一行调用processLRC去将此行数据解析保存到Lyrc中.
(2)在processLRC中处理一行中多个时间戳的逻辑, 因为有的歌词文件为了方便把重复的歌词的时间放在一起。
(3)将获取的解析数据按照timePoint排序, 因为防止(2)中描述的情况,重复歌词的时间放在一起的问题,如果不排序的话,会导致歌词顺序乱套.
(4)计算每行歌词停留的时间
什么是为了防止重复歌词放在一行上?
歌词是这样的:
[02:17.62][00:27.46]为你我用了半年的积蓄 [02:21.05][00:31.99]飘洋过海的来看你 [02:24.81][00:35.60]为了这次相聚 [02:27.59][00:38.16]我连见面时的呼吸都曾反复练习 [02:33.48][00:43.79]言语从来没能将我的情谊表达千万分之一 [02:40.91][00:51.47]为了这个遗憾 [02:44.19][00:54.65]我在夜里想了又想不肯睡去 [02:50.40][01:00.88]记忆它总是慢慢的积累 [02:54.50][01:04.92]在我心中无法抹去 [02:58.22][01:07.93]为了你的承诺 [03:00.77][01:10.72]我在最绝望的时候都忍住不哭泣 [03:08.46][01:17.06]陌生的城市啊! [03:12.81][01:22.20]熟悉的角落里 [03:16.63][01:26.99]也曾彼此安慰 [03:19.31][01:30.13]也曾相拥叹息 [03:21.36][01:31.72]不管将会面对什么样的结局 [03:58.95][03:25.36][01:35.83]在漫天风沙里 望着你远去 [04:02.66][03:29.38][01:39.70]我竟悲伤的不能自己 [04:06.82][03:34.02][01:43.83]多盼能送君千里 [04:09.25][03:35.95][01:46.15]直到山穷水尽 [04:11.46][03:38.51][01:58.96][01:48.44]一生和你相依
一句歌词前面多个时间tag.
2. 编写自定义TextView 去显示歌词
(1)继承TextView, 处理onDraw方法
(2)歌词绘制,区分当前行与普通行, 将当前行绘制在控件中心。指定两种Paint,来绘制两种不同文本。
(3)每隔一个时间段(就是Lyrc中的sleepTime字段)更新显示内容,向上滚动
public class LricView extends TextView { private List<Lyric> lyricList; // 标记当前行 private int currentLine = 0; private Paint currentPaint; private Paint otherPaint; private int currentColor = Color.GREEN; private int currentTextSize = 18; private int otherColor = Color.BLACK; private int otherTextSize = 15; // 行间距 private int lineSpace = 25; //当前歌词字体 private Typeface currentTypeface = Typeface.DEFAULT_BOLD; //其他歌词字体 private Typeface otherTypeface = Typeface.SERIF; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { invalidate(); // 刷新,会再次调用onDraw方法 super.handleMessage(msg); } }; public LricView(Context context, AttributeSet attrs) { super(context, attrs); currentPaint = new Paint(); otherPaint = new Paint(); lyricList = LrcUtils.readLRC(new File("/data/local/tmp/123456.lrc")); currentPaint.setColor(currentColor); currentPaint.setTextSize(currentTextSize); currentPaint.setTextAlign(Align.CENTER); // 画在中间 currentPaint.setTypeface(currentTypeface); otherPaint.setColor(otherColor); otherPaint.setTextSize(otherTextSize); otherPaint.setTextAlign(Align.CENTER); otherPaint.setTypeface(otherTypeface); } @Override protected void onDraw(Canvas canvas) { if (lyricList != null && currentLine < lyricList.size()) { Lyric lyrc = null; //绘制播放过的歌词 for (int i = currentLine - 1; i >= 0; i--) { lyric = lyricList.get(i); canvas.drawText(lyrc.lricString, getWidth() / 2, getHeight() / 2 + lineSpace * (i - currentLine), otherPaint); } lyric = lyrcList.get(currentLine); // 绘制正在播放的歌词 canvas.drawText(lyrc.lricString, getWidth() / 2, getHeight() / 2, currentPaint); //绘制未播放的歌词 for (int i = currentLine + 1; i < lyrcList.size(); i++) { lyric = lyricList.get(i); canvas.drawText(lyrc.lricString, getWidth() / 2, getHeight() / 2 + lineSpace * (i - currentLine), otherPaint); } lyric = lyricList.get(currentLine); handler.sendEmptyMessageDelayed(10, lyrc.sleepTime); currentLine++; } else { canvas.drawText("未找到歌词", getWidth() / 2, getHeight() / 2, currentPaint); } super.onDraw(canvas); } }
在自定义LricView中调用LrcUtils.readLRC方法传入歌词文件获取歌词信息,然后通过handler去控制多长时间进行绘制. LricView 可以直接使用.
3. 使用LricView
<RelativeLayout 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" tools:context=".MainActivity" > <com.example.lrcdemo.LricView android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
在布局文件中直接使用即可.
最终实现结果:
自此歌词处理完成, 贴出的代码都是可以直接使用参考, 关于快进快退音乐和倍速播放,歌词的处理逻辑就是修改lyric中的SleepTime, 不过逻辑也挺绕的. 这个歌词处理逻辑是我在做音乐播放器时参考学习别的教程, 在这里整理出来给大家参考, 如果有任何问题可以留言.
相关文章推荐
- Android 监听ScrollView滑动距离简单处理
- Android studio 错误提示,英文转中文
- android Gradle 教程
- Android Scroller分析
- 解决Gradle DSL method not found: ‘android()’
- Lucene highlighter高亮显示
- android studio 学习笔记之 生成签名APK
- android contentprovider
- Android开发艺术探索首观
- Android Studio导入ZXING
- Android ViewPager之实现轮播广告效果
- android packagemanager
- android 系统给应用的jar
- Android工作总结
- android studio 学习笔记之 注释规范化
- Android利用Fragment实现新闻客户端界面切换(addBackStack,popBackStack)
- Android Studio解决导入开源项目出现的相关问题
- android 性能优化之布局优化-----ViewStub
- android AsyncTask介绍
- 因为Android M权限问题导致的"Permission Denial: reading com.android.providers.media.MediaProvider"解决办法