android音乐播放器开发 SweetMusicPlayer 智能匹配本地歌词
上一篇写了使用MediaPlayer播放音乐,http://blog.csdn.net/huweigoodboy/article/details/39861539,现在来将一下加载本地歌词。好了,还是用那张图。
一,从内存卡上匹配歌词
将会从以下路径匹配
1) SweetMusicPlayer/Lyrics/
2) 歌曲同级目录下
3) 歌曲父级目录/lryics(Lryic加不加s,首字母大小与否又分情况)
LrcContent
package com.huwei.sweetmusicplayer.models; public class LrcContent { private String lrcStr; //歌词内容 private int lrcTime; //当前歌词时间 public String getLrcStr() { return lrcStr; } public void setLrcStr(String lrcStr) { this.lrcStr = lrcStr; } public int getLrcTime() { return lrcTime; } public void setLrcTime(int lrcTime) { this.lrcTime = lrcTime; } }LrcProcess
package com.huwei.sweetmusicplayer.models; 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.Collections; import java.util.List; import android.util.Log; import com.huwei.sweetmusicplayer.comparator.LrcComparator; import com.huwei.sweetmusicplayer.datamanager.MusicManager; import com.huwei.sweetmusicplayer.util.OnlineLrcUtil; import com.huwei.sweetmusicplayer.util.TimeUtil; public class LrcProcess { private List<LrcContent> lrclists; public LrcProcess() { super(); lrclists = new ArrayList<LrcContent>(); lrclists.clear(); } public String loadLrc(Song song) { String path = song.getPath(); StringBuffer stringBuffer = new StringBuffer(); // 得到歌词文件路径 String lrcPathString = path.substring(0, path.lastIndexOf(".")) + ".lrc"; int index = lrcPathString.lastIndexOf("/"); String parentPath; String lrcName; // if(index!=-1){ parentPath = lrcPathString.substring(0, index); lrcName = lrcPathString.substring(index); // } File file = new File(lrcPathString); // 匹配SweetMusicPlayer/Lyrics if (!file.exists()) { file = new File(OnlineLrcUtil.getInstance().getLrcPath( song.getTitle(), song.getArtist())); } Log.i("Path", file.getAbsolutePath().toString()); // 匹配Lyrics if (!file.exists()) { file = new File(parentPath + "/../" + "Lyrics/" + lrcName); } Log.i("Path", file.getAbsolutePath().toString()); // 匹配lyric if (!file.exists()) { file = new File(parentPath + "/../" + "lyric/" + lrcName); } Log.i("Path", file.getAbsolutePath().toString()); // 匹配Lyric if (!file.exists()) { file = new File(parentPath + "/../" + "Lyric/" + lrcName); } Log.i("Path", file.getAbsolutePath().toString()); // 匹配lyrics if (!file.exists()) { file = new File(parentPath + "/../" + "lyrics/" + lrcName); } Log.i("Path", file.getAbsolutePath().toString()); if (!file.exists()) { stringBuffer.append(MusicManager.OperateState.READLRCFILE_FAIL); return stringBuffer.toString(); } try { FileInputStream fin = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fin, "utf-8"); BufferedReader br = new BufferedReader(isr); String s; boolean isLrc = false; while ((s = br.readLine()) != null) { // if(isLrc){ s = s.replace("[", ""); // 去掉左边括号 String lrcData[] = s.split("]"); // 这句是歌词 if (lrcData[0].matches("^\\d{2}:\\d{2}.\\d+$")) { int len = lrcData.length; int end = lrcData[len - 1].matches("^\\d{2}:\\d{2}.\\d+$") ? len : len - 1; for (int i = 0; i < end; i++) { LrcContent lrcContent = new LrcContent(); int lrcTime = TimeUtil.getLrcMillTime(lrcData[i]); lrcContent.setLrcTime(lrcTime); if (lrcData.length == end) lrcContent.setLrcStr(""); // 空白行 else lrcContent.setLrcStr(lrcData[len - 1]); lrclists.add(lrcContent); } } } // 按时间排序 Collections.sort(lrclists, new LrcComparator()); if (lrclists.size() == 0) { stringBuffer.append(MusicManager.OperateState.READLRC_LISTNULL); } else { stringBuffer.append(MusicManager.OperateState.READLRC_SUCCESS); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); // stringBuffer.append("未找到歌词文件"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); // stringBuffer.append("不支持的编码"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); // stringBuffer.append("IO错误"); } return stringBuffer.toString(); } public List<LrcContent> getLrclists() { return lrclists; } }
二,歌词解析
先摘取一段歌词ti:安静]
[ar:周杰伦]
[al:范特西]
[by:Midas]
[00:03.16]
[00:04.50]周杰伦-安静
[00:14.50]词:周杰伦 曲:周杰伦 编:钟兴民
[00:25.17]
[02:27.48][00:27.02]只剩下钢琴陪我谈了一天
[02:32.76][00:32.51]睡着的大提琴 安静的旧旧的
[02:39.60][00:38.67]
[02:40.74][00:40.48]我想你已表现的非常明白
[02:46.04][00:45.7]我懂我也知道 你没有舍不得
[02:53.23][00:52.53]
[02:54.04][00:53.76]你说你也会难过我不相信
[03:00.59][01:00.36]牵着你陪着我 也只是曾经
[03:06.63][01:06.24]希望他是真的比我还要爱你
[03:13.29][01:12.96]我才会逼自己离开
每次遍历一行,首先要把“[”替换成" ",去匹配哪些是时间部分,正则匹配“^\\d{2}:\\d{2}.\\d+$”,然后split("]"),得到一个数组data[],最后一个是内容,前面是歌词,遍历数组,装入时间歌词到list。
时间处理:转成毫秒
遍历所有行后,对list按照时间排序。
代码在上面LrcProgress。
三,LrcView控件
分为以下状态:public static String READLRC_SUCCESS="READLRC_SUCCESS";
//读取本地歌词成功
public static String READLRC_LISTNULL="READLRC_LISTNULL";
//读取歌词list为null
public static String READLRC_ONLINE="READLRC_ONLINE";
//正在从网络加载歌词
public static String READLRCFILE_FAIL="READLRCFILE_FAIL";
//读取歌词文件失败
public static String READLRCONLINE_FAIL="READLRCONLINE_FAIL";
//从网络加载歌词失败
根据不同的状态绘制不同的内容。
LrcView继承自ScrollView,然后再加一层LinearLayout,歌词绘制在TextView上,按照播放时间滚动,就可以保证当前播放的歌词在屏幕中间了。
关于自定义控件,要注意对onMeasure(),onLayout(),onDraw()比较好的理解,有时候遇到onDraw()不能执行,记得加上setWillNotDraw(false),这里直接继承自ScrollView,就不需要考虑那么多了。
这里需要根据播放时间计算当前播放位置,歌词所在行,然后不同的时候,就去更新歌词界面。
调整歌词进度:
触摸监听时,ACTION_MOVE去绘制歌词进度预览(包括调整到的时间预览),ACTION_UP时调整到对应的进度。
package com.huwei.sweetmusicplayer.ui.widgets; import java.util.List; import com.huwei.sweetmusicplayer.datamanager.MusicManager; import com.huwei.sweetmusicplayer.models.LrcContent; import com.huwei.sweetmusicplayer.util.TimeUtil; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnScrollChangedListener; import android.widget.ScrollView; import android.widget.TextView; import android.view.View.OnTouchListener; public class LrcView extends ScrollView implements OnScrollChangedListener,OnTouchListener{ private float width; //歌词视图宽度 private float height; //歌词视图高度 private Paint currentPaint; //当前画笔对象 private Paint notCurrentPaint; //非当前画笔对象 private final float textHeight=40; //文本高度 private final float textSize=36; //高亮文本大小 private final float notTextSize=30; //非高亮文本大小 private int index; //歌词list集合下标 private String lrcState; private LrcTextView lrcTextView; private List<LrcContent> lrcLists; private int scrollY; private boolean canDrawLine=false; private int pos=-1; //手指按下后歌词要到的位置 private Paint linePaint; private boolean canTouchLrc=false; //是否可以触摸并调整歌词 private int count=0; //绘制加载点的次数 private Context mContext; public LrcView(Context context) { this(context,null); // TODO Auto-generated constructor stub } public LrcView(Context context, AttributeSet attrs) { this(context, attrs,0); // TODO Auto-generated constructor stub } public LrcView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub mContext=context; this.setOnTouchListener(this); init(); } public List<LrcContent> getLrcLists() { return lrcLists; } public void setLrcLists(List<LrcContent> lrcLists) { this.lrcLists = lrcLists; //判断歌词界面是否可以触摸 if(lrcLists==null||lrcLists.size()==0) canTouchLrc=false; else canTouchLrc=true; //设置index=-1 this.index=-1; LayoutParams params1=new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT); lrcTextView=new LrcTextView(this.getContext()); lrcTextView.setLayoutParams(params1); this.removeAllViews(); this.addView(lrcTextView); } public int getIndex() { return index; } public void setIndex(int index) { //歌曲位置发生变化,而且手指不是调整歌词位置的状态 if(this.index!=index&&pos==-1){ this.scrollTo(0, (int)(index*textHeight)); } this.index = index; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); this.width=w; this.height=h; } public int getIndexByLrcTime(int currentTime){ for(int i=0;i<lrcLists.size();i++){ if(currentTime<lrcLists.get(i).getLrcTime()){ return i-1; } } return lrcLists.size()-1; } public void clear(){ lrcLists=null; } public String getLrcState() { return lrcState; } public void setLrcState(String lrcState) { this.lrcState = lrcState; this.invalidate(); } class LrcTextView extends TextView{ public LrcTextView(Context context) { this(context,null); // TODO Auto-generated constructor stub } public LrcTextView(Context context, AttributeSet attrs) { this(context, attrs,0); // TODO Auto-generated constructor stub } public LrcTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub this.setWillNotDraw(false); } //绘制歌词 @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); Log.i("LrcTextView onDraw","LrcTextView onDraw"); if(canvas==null) return; int tempY=(int) height/2; if(MusicManager.OperateState.READLRC_LISTNULL.equals(lrcState)){ canvas.drawText("歌词内容为空", width/2, tempY, notCurrentPaint); return; }else if(MusicManager.OperateState.READLRCFILE_FAIL.equals(lrcState)){ canvas.drawText("未找到歌词文件", width/2, tempY, notCurrentPaint); return; } else if(MusicManager.OperateState.READLRC_SUCCESS.equals(lrcState)){ //绘制歌词 for(int i=0;i<lrcLists.size();i++,tempY+=textHeight){ if(i==index){ canvas.drawText(lrcLists.get(i).getLrcStr(), width/2, tempY, currentPaint); }else if(i==pos){ canvas.drawText(lrcLists.get(i).getLrcStr(), width/2, tempY, linePaint); }else{ canvas.drawText(lrcLists.get(i).getLrcStr(), width/2, tempY, notCurrentPaint); } } return; }else if(MusicManager.OperateState.READLRC_ONLINE.equals(lrcState)){ String drawContentStr="在线匹配歌词"; for(int i=0;i<count;i++){ drawContentStr+="."; } count++; if(count>=6) count=0; canvas.drawText(drawContentStr, width/2, tempY, notCurrentPaint); handler.sendEmptyMessageDelayed(1, 500); return; }else if(MusicManager.OperateState.READLRCONLINE_FAIL.equals(lrcState)){ canvas.drawText("从网络加载歌词失败", width/2, tempY, notCurrentPaint); return; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); heightMeasureSpec=(int) (height+textHeight*(lrcLists.size()-1)); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } }; @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); if(canDrawLine){ canvas.drawLine(0, scrollY+height/2, width, scrollY+height/2, linePaint); canvas.drawText(TimeUtil.toTime(lrcLists.get(pos).getLrcTime()), 42, scrollY+height/2-2, linePaint); } } private void init(){ setFocusable(true); //设置该控件可以有焦点 this.setWillNotDraw(false); //高亮歌词部分 currentPaint=new Paint(); currentPaint.setAntiAlias(true); //设置抗锯齿 currentPaint.setTextAlign(Paint.Align.CENTER); //设置文本居中 //非高亮歌词部分 notCurrentPaint=new Paint(); notCurrentPaint.setAntiAlias(true); notCurrentPaint.setTextAlign(Paint.Align.CENTER); // linePaint=new Paint(); linePaint.setAntiAlias(true); linePaint.setTextAlign(Paint.Align.CENTER); //设置画笔颜色 currentPaint.setColor(Color.argb(210, 251, 248, 29)); notCurrentPaint.setColor(Color.argb(140, 255, 255, 255)); linePaint.setColor(Color.RED); currentPaint.setTextSize(textSize); currentPaint.setTypeface(Typeface.SERIF); notCurrentPaint.setTextSize(notTextSize); notCurrentPaint.setTypeface(Typeface.DEFAULT); linePaint.setTextSize(textSize); linePaint.setTypeface(Typeface.SERIF); } @Override public void invalidate() { // TODO Auto-generated method stub super.invalidate(); lrcTextView.invalidate(); } @Override public void onScrollChanged() { // TODO Auto-generated method stub } @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub //界面不能被触摸 if(!canTouchLrc) return true; switch(event.getAction()){ case MotionEvent.ACTION_MOVE: scrollY=this.getScrollY(); pos=(int) (this.getScrollY()/textHeight); canDrawLine=true; this.invalidate(); Log.i("LrcView", "ACTION_DOWN"); break; case MotionEvent.ACTION_UP: MusicManager.getInstance().setProgress(lrcLists.get(pos).getLrcTime()); canDrawLine=false; pos=-1; this.invalidate(); break; } return false; } private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub invalidate(); } }; }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。