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();
		}

	};
}
时间: 2024-08-27 11:26:27

android音乐播放器开发 SweetMusicPlayer 智能匹配本地歌词的相关文章

android音乐播放器开发 SweetMusicPlayer 智能负载直插式歌词

在一份书面的使用MediaPlayer播放音乐, http://blog.csdn.net/huweigoodboy/article/details/39862773.假设没有本地歌词怎么办?如今来将一下载入在线歌词.好了,还是用那张图. 在实现这个功能的时候,lz尝试过baidu api,歌词迷api,后来选用了歌词迷api.尽管还是资源不全.并且还有非常多错误. 特别头疼的是有时候歌词竟然不分行.解析起来简直难受. 歌词迷api歌词查询地址:http://geci.me/api/lyric/

android音乐播放器开发 SweetMusicPlayer 智能加载在线歌词

上一篇写了使用MediaPlayer播放音乐, http://blog.csdn.net/huweigoodboy/article/details/39862773,如果没有本地歌词怎么办?现在来将一下加载在线歌词.好了,还是用那张图. 在实现这个功能的时候,lz尝试过baidu api,歌词迷api,后来选用了歌词迷api,虽然还是资源不全,而且还有很多错误.特别头疼的是有时候歌词居然不分行,解析起来简直难受. 歌词迷api歌词查询地址:http://geci.me/api/lyric/ 比如

android音乐播放器开发 SweetMusicPlayer 加载歌曲列表

上一篇写了播放器的整体实现思路,http://blog.csdn.net/huweigoodboy/article/details/39855653,现在来总结下加载歌曲列表. 比较好的实现思路就是,自己维护一个SQLite数据库,然后音乐信息都从sd卡上扫描,好处有很多,但是这样做的话代码量会比较大,写了一段扫描sd卡的代码,然后发现扫描音乐的速度简直慢的惊人,可能自己的目录太多,太深,目前还没想到一个比较好的算法去快速扫描sd卡. 楼主比较偷懒,android自己本身有一个关于媒体信息的数据

android音乐播放器开发 SweetMusicPlayer 载入歌曲列表

上一篇写了播放器的总体实现思路,http://blog.csdn.net/huweigoodboy/article/details/39855653,如今来总结下载入歌曲列表. 代码地址:https://github.com/huweigoodboy/SweetMusicPlayer 比較好的实现思路就是.自己维护一个SQLite数据库,然后音乐信息都从sd卡上扫描,优点有非常多,可是这样做的话代码量会比較大,写了一段扫描sd卡的代码.然后发现扫描音乐的速度简直慢的惊人,可能自己的文件夹太多,太

android音乐播放器开发 SweetMusicPlayer 播放本地音乐

上一篇写了加载歌曲列表,http://blog.csdn.net/huweigoodboy/article/details/39856411,现在来总结下播放本地音乐. 一,MediaPlayer 首先来看看MediaPlayer的生命周期: Idle 状态:当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态.这两种方法的一个重要差别就是:如果在这个状态下调用了getDuration()等方法(相当于调用时机不正确

android音乐播放器开发 SweetMusicPlayer 摇一摇换歌

上一篇写了如何在线匹配歌词,http://blog.csdn.net/huweigoodboy/article/details/39878063,现在来讲讲摇一摇功能开发. 同样用了一个Service去实现摇一摇. ShakeListener继承自SensorEventListener,当加速度感应器感受到重力变化,就去通知onShakeListener调用onShake(), 在震动的同时随机一首歌. 一,加速度感应器 在这里需要设置一个速度阈值和时间间隔,控制一定的时间间隔才能触发第二次震动

android音乐播放器开发教程

android音乐播放器开发教程 android音乐播放器开发教程,布布扣,bubuko.com

Android音乐播放器开发

今日看书,看到这个播放器,我就写了个例子,感觉还行,这个播放器能播放后缀是.MP3的音乐,这个例子在main.xml设置listView的时候,注意:android:id="@+id/android:list"的设置,否则程序会报错,说找不到listview.这个效果还是不错的.可以当做是简单的音乐播放器,可以读取sdcard里面后缀是.MP3的歌曲.有问题可以留言,想要源码可以留言,这个代码比较简单.转载请标明出处: http://blog.csdn.net/wdaming1986/

Android音乐播放器源码(歌词.均衡器.收藏.qq5.0菜单.通知)

Android音乐播放器(歌词.均衡器.收藏.qq5.0菜单.通知) 一款Android音乐播放器源码,基本功能都实现了 qq5.0菜单(歌词.均衡器.收藏.qq5.0菜单.通知) 只有向右滑动出现,菜单键和指定按钮都还没有添加. 下载地址:http://www.devstore.cn/code/info/1144.html 运行截图:     热门源码下载: 高仿京东商城 Android快速开发不可或缺的11个工具类 Android快速开发框架LoonAndroid Android应用源码比较