android音乐播放器开发 SweetMusicPlayer 智能匹配本地歌词
上一篇写了使用MediaPlayer播放音乐,http://blog.csdn.net/huweigoodboy/article/details/39861539。
代码地址:https://github.com/huweigoodboy/SweetMusicPlayer
如今来将一下载入本地歌词。好了,还是用那张图。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHV3ZWlnb29kYm95/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
一。从内存卡上匹配歌词
将会从下面路径匹配
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();
} };
}
下一篇智能匹配在线歌词: http://blog.csdn.net/huweigoodboy/article/details/39878063
android音乐播放器开发 SweetMusicPlayer 智能匹配本地歌词的更多相关文章
- android音乐播放器开发 SweetMusicPlayer
智能负载直插式歌词
在一份书面的使用MediaPlayer播放音乐, http://blog.csdn.net/huweigoodboy/article/details/39862773.假设没有本地歌词怎么办?如今来将 ...
- android音乐播放器开发 SweetMusicPlayer 播放本地音乐
上一篇写了载入歌曲列表,http://blog.csdn.net/huweigoodboy/article/details/39856411,如今来总结下播放本地音乐. 一,MediaPlayer 首 ...
- android音乐播放器开发 SweetMusicPlayer 实现思路
一,实现效果 眼下还不是特别完好,主要有下面几个功能, 1,载入歌曲列表(实现a-z字母检索) 2,播放本地音乐 3.智能匹配本地歌词 4.智能载入在线歌词(事实上不算智能.发现歌词迷api提供的歌词 ...
- android音乐播放器开发 SweetMusicPlayer 载入歌曲列表
上一篇写了播放器的总体实现思路,http://blog.csdn.net/huweigoodboy/article/details/39855653,如今来总结下载入歌曲列表. 代码地址:https: ...
- android音乐播放器开发 SweetMusicPlayer 摇一摇换歌
上一篇写了怎样在线匹配歌词,http://blog.csdn.net/huweigoodboy/article/details/39878063,如今来讲讲摇一摇功能开发. 相同用了一个Service ...
- android音乐播放器开发教程
android音乐播放器开发教程 Android扫描sd卡和系统文件 Android 关于录音文件的编解码 实现米聊 微信一类的录音上传的功能 android操作sdcard中的多媒体文件——音乐列表 ...
- Android音乐播放器开发
今日看书,看到这个播放器,我就写了个例子,感觉还行,这个播放器能播放后缀是.MP3的音乐,这个例子在main.xml设置listView的时候,注意:android:id="@+id/and ...
- Android音乐播放器源码(歌词.均衡器.收藏.qq5.0菜单.通知)
一款Android音乐播放器源码,基本功能都实现了 qq5.0菜单(歌词.均衡器.收藏.qq5.0菜单.通知) 只有向右滑动出现,菜单键和指定按钮都还没有添加. 源码下载:http://code.66 ...
- 一款非常简单的android音乐播放器源码分享给大家
一款非常简单的android音乐播放器源码分享给大家,该应用虽然很小,大家常用的播放器功能基本实现了,可能有点还不够完善,大家也可以自己完善一下,源码在源码天堂那里已经有了,大家可以到那里下载学习吧. ...
随机推荐
- 4星|《DK商业百科》:主要商业思想与事件的概括
全书分为以下6章:1:企业的起步与发展:2:领导和人力资源:3:理财:4:战略和运营:5:营销管理:6:生产与生产后.每章有拆分为成多个比较小的专题,阐述相关专题的主要的商业思想与实践. 基本是作者按 ...
- MFC_2.4 组合框和图片控件
组合框和图片控件 1.拖控件 图片属性更改Type 为Bitmap 名字也要改,不能为IDC_STATIC 绑定变量控件,重命名. 2.初始化 // 设置一个定时器,用于更新图片 SetTimer(0 ...
- Vue的特性
1.数据驱动视图 <div id="app"> <p> {{ message }}<p> </div> var app = new ...
- 09Microsoft SQL Server 表数据插入,更新,删除
Microsoft SQL Server 表数据插入,更新,删除 向表中插入数据 INSERT INTO insert into tb1 values(0004,'张凤凤') insert into ...
- 17Oracle Database 维护
Oracle Database 维护 备份 还原
- 微信支付开发 c#
代码demo下载地址: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
- JAVA基础——链表结构之单链表
链表:一种数据存储结构.学链表首先要搞懂数组,按朋友的话说,数组和链表的关系就相当于QQ2008和QQ2009. 除非要通过索引频繁访问各个数据,不然大多数情况下都可以用链表代替数组. 链表部分主要要 ...
- Android 各大网络请求库的比较及实战
自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection ...
- Humidex POJ - 3299 (数学)
题目大意 给定你三个变量中的两个输出剩下的那一个 题解 没有什么,就是把公式推出来即可,完全的数学题 代码 #include <iostream> #include <cmath&g ...
- python3中post请求里带list报错
这个post请求的数据太长,一般data=,json=就够了. 但是今天这个一直报错,用json吧,报缺少参数,用data吧,报多余[. 后来改成data=,并把数据中的[] 用引号括起来," ...