手机影音第十七天,实现歌词同步
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手机影音第十七天,实现歌词同步相关的知识,希望对你有一定的参考价值。
代码已托管到码云,有兴趣的小伙伴可以下载看看
https://git.oschina.net/joy_yuan/MobilePlayer
效果图:
有一个小的遗憾,就是该MP3文件和歌词文件要在同一路径下,才能读取到歌词,否则读取不到录音文件。
将录音文件发到这里,是.lrc格式的文件,其实TXT文件的也行;如果在手机上显示是乱码的话,就改一下文件的编码为Unicode,再尝试下。
[ti:北京北京] [00:00.05]献给我最爱的老婆 --常长丽 [00:02.17]歌曲名:北京北京 [00:04.00]演唱:汪峰 [00:06.84]原立鹏 制 [00:08.62] 献给我最爱的老婆 --常长丽 [00:31.16]当我走在这里的每一条街道 [00:37.32]我的心似乎从来都不能平静 [00:45.23]就让花朵妒忌红名和电气致意 [00:51.66]我似乎听到了他这不慢的心跳 [00:59.74]我在这里欢笑我在这里哭泣 [01:06.93]我在这里活着也在这死去 [01:14.09]我在这里祈祷 我在这里迷惘 [01:21.25]我在这里寻找 在这里寻求 [04:11.76][04:04.59][02:31.70][01:27.19]北京 北京 [01:35.46]咖啡管与广场又散着天气 [01:41.73]就象夜空的哪月亮的距离 [01:49.80]人们在挣扎中相互告慰和拥抱 [01:56.14]寻找着 追著着 夜夜时的睡梦 [02:04.19]我在这欢笑 我们在这哭泣 [02:11.23]我在这活着也在这死去 [02:18.70]我在这祈祷 我在这迷惘 [02:25.76]我在这寻找 在这追求 [03:08.56]如果有一天我不得不离去 [03:14.55]我希望人们把我埋葬在这里 [03:22.95]在这忘了感觉到我在存在 [03:29.10]在这有太多有我眷恋的东西 [03:37.38]我在这欢笑 我在这哭泣 [03:44.55]我在这里活着也在这死去 [03:51.74]我在这里祈祷 我在这里迷惘 [03:58.90]我在这里寻找 也在这死去 [04:04.35]北京 北京 [04:11.48]北京 北京
1、新建一个解析歌词的类,接收一个File类型的文件,由于解析的类太复杂,就不在这里分析,直接贴源码,大家以后用的时候直接参考即可
package com.yuanlp.mobileplayer.utils; import com.yuanlp.mobileplayer.bean.Lyrc; import java.io.BufferedInputStream; 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; /** * Created by 原立鹏 on 2017/7/31. * 解析歌词工具类 */ public class LyrcUtils { /** * 得到解析好的歌词列表 * @return */ public ArrayList<Lyrc> getLyrcs() { return Lyrcs; } private ArrayList<Lyrc> Lyrcs; /** * 是否存在歌词 * @return */ public boolean isExistsLyrc() { return isExistsLyrc; } /** * 是否存在歌词 */ private boolean isExistsLyrc = false; /** * 读取歌词文件 * @param file /mnt/scard/audio/beijingbeijing.txt */ public void readLyrcFile(File file){ if(file == null || !file.exists()){ //歌词文件不存在 Lyrcs = null; isExistsLyrc = false; }else{ //歌词文件存在 //1.解析歌词 一行的读取-解析 Lyrcs = new ArrayList<>(); isExistsLyrc = true; BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),getCharset(file))); String line = ""; while ((line = reader.readLine())!= null){ line = parsedLyrc(line);// } reader.close(); } catch (Exception e) { e.printStackTrace(); } //2.排序 Collections.sort(Lyrcs, new Comparator<Lyrc>() { @Override public int compare(Lyrc lhs, Lyrc rhs) { if(lhs.getTimePosition() < rhs.getTimePosition()){ return -1; }else if(lhs.getTimePosition() > rhs.getTimePosition()){ return 1; }else{ return 0; } } }); //3.计算每句高亮显示的时间 for(int i=0;i<Lyrcs.size();i++){ Lyrc oneLyrc = Lyrcs.get(i); if(i+1 < Lyrcs.size()){ Lyrc twoLyrc = Lyrcs.get(i+1); oneLyrc.setSleepTime(twoLyrc.getTimePosition()-oneLyrc.getTimePosition()); } } } } /** * 判断文件编码 * @param file 文件 * @return 编码:GBK,UTF-8,UTF-16LE */ public 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); 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; } /** * 解析一句歌词 * @param line [02:04.12][03:37.32][00:59.73]我在这里欢笑 * @return */ private String parsedLyrc(String line) { ////indexOf第一次出现[的位置 int pos1 = line.indexOf("[");//0,如果没有返回-1 int pos2 = line.indexOf("]");//9,如果没有返回-1 if(pos1 ==0 && pos2 != -1){//肯定是由一句歌词 //装时间 long[] times = new long[getCountTag(line)]; String strTime =line.substring(pos1+1,pos2) ;//02:04.12 times[0] = strTime2LongTime(strTime); String content = line; int i = 1; while (pos1 ==0 && pos2 != -1){ content = content.substring(pos2 + 1); //[03:37.32][00:59.73]我在这里欢笑--->[00:59.73]我在这里欢笑-->我在这里欢笑 pos1 = content.indexOf("[");//0/-1 pos2 = content.indexOf("]");//9//-1 if(pos2 != -1 ){ strTime = content.substring(pos1 + 1, pos2);//03:37.32-->00:59.73 times[i] = strTime2LongTime(strTime); if(times[i] == -1){ return ""; } i++; } } Lyrc Lyrc = new Lyrc(); //把时间数组和文本关联起来,并且加入到集合中 for(int j = 0;j < times.length;j++){ if(times[j] !=0){//有时间戳 Lyrc.setContent(content); Lyrc.setTimePosition(times[j]); //添加到集合中 Lyrcs.add(Lyrc); Lyrc = new Lyrc(); } } return content;//我在这里欢笑 } return ""; } /** * 把String类型是时间转换成long类型 * @param strTime 02:04.12 * @return */ private long strTime2LongTime(String strTime) { long result = -1; try{ //1.把02:04.12按照:切割成02和04.12 String[] s1 = strTime.split(":"); //2.把04.12按照.切割成04和12 String[] s2 = s1[1].split("\\."); //1.分 long min = Long.parseLong(s1[0]); //2.秒 long second = Long.parseLong(s2[0]); //3.毫秒 long mil = Long.parseLong(s2[1]); result = min * 60 * 1000 + second * 1000 + mil*10; }catch (Exception e){ e.printStackTrace(); result = -1; } return result; } /** * 判断有多少句歌词 * @param line [02:04.12][03:37.32][00:59.73]我在这里欢笑 * @return */ private int getCountTag(String line) { int result = -1; String [] left = line.split("\\["); String [] right = line.split("\\]"); if(left.length==0 && right.length ==0){ result = 1; }else if(left.length > right.length){ result = left.length; }else{ result = right.length; } return result; } }
2、在audioplayerActivity里,接收到service发送的EventBus的准备好播放的事件后,获取该MP3 文件的路径,最后获取歌词并解析,然后发送到handler里来显示歌词
/** * 订阅eventbus */ @Subscribe(threadMode=ThreadMode.MAIN,sticky = false,priority = 99) public void showData(MediaItem item) { showLyrc(); //去获取MP3文件的路径然后获取歌词文件,并解析歌词,最终显示歌词 showViewData(); checkPlayMode(); } /** * 去获取MP3文件的路径然后获取歌词文件,并解析歌词,最终显示歌词 */ public void showLyrc() { LyrcUtils lyrcUtils=new LyrcUtils(); try { //得到录音文件的地址 String path=mservice.getAudioPath(); path=path.substring(0,path.lastIndexOf(".")); File file=new File(path+".lrc"); //先去查找lrc格式的歌词文件 if (!file.exists()){ file=new File(path+".txt"); //拼一个TXT格式的歌词文件 } lyrcUtils.readLyrcFile(file); showlyrcView.setLyrcs(lyrcUtils.getLyrcs()); if (lyrcUtils.isExistsLyrc()){ //存在歌词,才发消息,否则不发消息 handler.sendEmptyMessage(SHOW_LYRC); } } catch (RemoteException e) { e.printStackTrace(); } }
最终实现了歌词的解析。
这样实现的歌词,匹配到不同的分辨率的手机上时,字的大小会不同,那么下面就是来解决这个显示字大小的修改,最终效果如下。
定义一个工具类,DensityUtil,里面代码如下:
/** * 根据手机的分辨率从 dip 的单位 转成为 px(像素) */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp */ public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); }
那么我们只需要在各自文字设置那里,通过这个工具类,修改下文字大小即可。
在ShowlyrcView类里,把textHeight=DensityUtil.dip2px(context,20);
然后2个画笔处,修改文字大小即可
//创建画笔----当前高亮的画笔 paint=new Paint(); paint.setColor(Color.GREEN); //高亮颜色 paint.setTextSize(DensityUtil.dip2px(context,20)); paint.setAntiAlias(true); //抗锯齿 paint.setTextAlign(Paint.Align.CENTER); //对齐方式,居中显示 //白色画笔 whitepaint=new Paint(); whitepaint.setColor(Color.WHITE); whitepaint.setTextAlign(Paint.Align.CENTER); whitepaint.setTextSize(DensityUtil.dip2px(context,20)); whitepaint.setAntiAlias(true);
最终达到了将歌词匹配屏幕的大小。
本文出自 “YuanGuShi” 博客,请务必保留此出处http://cm0425.blog.51cto.com/10819451/1952517
以上是关于手机影音第十七天,实现歌词同步的主要内容,如果未能解决你的问题,请参考以下文章