Android歌词播放的实现
2016-07-27 14:22
615 查看
公司项目最近有需求,要实现一个音乐系统,涉及到一个歌词播放的功能,现将这个实现过程写下来。
做之前先上网查了下相关APP的情况,发现QQ音乐的桌面歌词效果,正是我想要的,显示两行歌词,轮流播放,并显示播放时的过渡效果。
首先分析的出,歌词有两行,那么应该是两个控件来的分别显示,然后通过一系列算法来控制两行歌词交替显示,这样的话,我们要先写出用于显示歌词的控件来。
播放方法的参数一定要有播放的时长,和一个播放结束完毕的监听,这个监听提供给调用者,这样才可以播放下一句歌词。
我先将整个播放代码贴上来:
代码上有我刚才提到的几个方法,比如设置歌词,设置字体大小,播放等方法,但仔细看下歌词的参数,仔细看第二个参数,这个参数是用于播放前等待时间,因为歌手在唱下一句的时候,会停留一些时间,会喘口气,休息一会。
歌词要播放的时候显示过渡效果的话,就还要做一些处理,这里的实现原理是,用两个不同颜色的TextView重叠着,我们动态设置上面的TextView的宽度,就可以实现我们要的过渡效果了。
LRC歌词是歌词前面会有对应的播放时间,这个时间用中括号括着,我们通过一点点解析算法,将其放到两个容器就好了。
歌词有两行,我们先开始命名,第一行歌词的时间叫“时间一”,歌词叫“歌词一”,第二行则叫“时间二”和“歌词二”。
如果歌词1为空,设置歌词1为下一句歌词,记录时间1-时间2为歌词2的等待时间
歌词1播放->时间2-时间1
我们要考虑到的情况有,在LRC歌词文件中,在歌手喘气休息的时候,是没歌词的,这种情况在LRC中,没歌词,有时间;
假设我们在播放第一行歌词的时候,就有这样的逻辑
如果歌词二为空,就获取下一句歌词作为歌词二,获取下一个时间为时间二
在歌词一播放完后,我们在播放完的回调中,再播放歌词二,这样循环下去,知道歌词结束。
这样我们就实现了歌词的交叉播放,但还存在一个问题,就是,播放的时候是一句接着一句的,如果手机性能比较差,就可能慢慢的,就会和音乐不同了,这个情况也是不允许出现的,所以开始升级。
升级后逻辑是:
记录开始时间
播放前获取现在时间,减去和歌词时间的差
歌词结束时间减去开始播放时间,算出速度
这样总体我们实现完了,效果还可以,就算手机再差,也会在播放下一句中的时候,准确对准歌词的时间。源码我上传到CSDN,再去下载看看具体逻辑吧,谢谢观看!
资源下载地址:http://download.csdn.net/detail/ysb794008002/9586853
做之前先上网查了下相关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
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories