android音乐播放器之歌词下载、处理、开始、同步
**
程序源代码在底部
**
先来看看效果
下载
/**
* 自定义下载方法,调用系统DownloadManager下载
*
* @param myUrl
* 下载所需要的链接
* @param path
* 本地SDcard中的文件夹
* @param fileName
* 保存到文件名
* @return void
* */
public void download(final String myUrl, final String path,
final String fileName) {
try {
Runnable networkTask = new Runnable() {
@Override
public void run() {
// 创建下载任务
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(myUrl));
// 设置为通知栏和下载界面皆不显示,需要添加权限DOWNLOAD_WITHOUT_NOTIFICATION
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
request.setVisibleInDownloadsUi(false);
// sdcard的目录下的mymusicplayer文件夹
request.setDestinationInExternalPublicDir(path, fileName);
// 将下载请求加入下载队列
DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
// 加入下载队列后会给该任务返回一个long型的id,
long id = downloadManager.enqueue(request);
// 根据不同类型赋值id
if (isDownloadLrc) {
Log.i("下载歌词开始", String.valueOf(id));
Log.i("歌词地址", myUrl);
isDownloadLrc = false;
lrcReference = id;
}
}
};
// 开启一个子线程,进行网络操作
new Thread(networkTask).start();
} catch (Exception e) {
e.printStackTrace();
}
}
下载完成后处理:
/**
* 初始化下载等待,下载任务完进行处理
*
* @return void
* */
private void initDownload() {
IntentFilter filter = new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
long reference = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, -1);
downloadSuccess(reference);
}
};
registerReceiver(receiver, filter);
}
/**
* 下载完成处理
*
* @param reference
* 下载序号id
* */
public void downloadSuccess(long reference) {
try {
if (reference == lrcReference) { // 处理歌词
Log.i("歌词下载完", String.valueOf(reference));
File file = new File(LrcPath);
if (!file.exists()) { // 本地文件不存在
return;
}
initLrc(LrcPath);
int temp = mMediaPlayer01.getCurrentPosition();
currentIndex = findIndexOfTime(temp);
lrcExits = true;
}
} catch (RuntimeException e) {
}
}
我调用了系统的DownloadManager下载,之前是自己写的,但有时候会出错,感觉系统这个下载也挺好用的,这函数用来下载其他文件也可以。定义一个广播接收器接收,下载完后,就进行处理.
接下来是处理,读取Lrc文件,这是一个类,在别人的基础上修改的,文末给上链接,希望大神莫怪
package com.example.musicplayer;
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.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.util.Log;
/**
* 歌词处理
* */
public class LrcRead {
private List<Map<String, Object>> word_Time_List = new ArrayList<Map<String, Object>>();
/**
* 处理歌词文件
*
* @param path
* 歌词路径
* @return void throws IndexOutOfBoundsException
* */
public void readLRC(String path) throws IndexOutOfBoundsException{
File file = new File(path);
try {
FileInputStream fileInputStream = new FileInputStream(file);
//这里的编码,我用utf-8,全部乱码,用gb2312和gbk中英文不会乱码,不知道咋滴
InputStreamReader inputStreamReader = new InputStreamReader(
fileInputStream, "gbk");
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String s = "";
//读取歌词
while ((s = bufferedReader.readLine()) != null) {
int time = addTimeToList(s); //添加歌词时间间
if(s.length()==0 || time == -1)
continue;
if ((s.indexOf("[ar:") != -1) || (s.indexOf("[ti:") != -1)
|| (s.indexOf("[by:") != -1)) {
s = s.substring(s.indexOf(":") + 1, s.indexOf("]"));
} else {
String ss = s.substring(s.indexOf("["), s.indexOf("]") + 1);
s = s.replace(ss, "");
}
Map<String, Object> map4 = new HashMap<String, Object>();
map4.put("time", time);
map4.put("word", s);
word_Time_List.add(map4);
}
bufferedReader.close();
inputStreamReader.close();
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
/**
* 获取歌词+时间列表
*
* @return 所有歌词
* */
public List<Map<String, Object>> getWordTimeList() {
return word_Time_List;
}
/**
* 分离出时间
*
* @param string
* 时间
* @return 毫秒
* */
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]);
// 计算上一行与下一行的时间转换为毫秒数
int currentTime = (minute * 60 + second) * 1000 + millisecond * 10;
return currentTime;
}
/**
* 添加时间到列表
*/
private int addTimeToList(String string) {
int t = -1;
Matcher matcher = Pattern.compile(
"\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string);
if (matcher.find()) {
String str = matcher.group();
t = timeHandler(str.substring(1, str.length() - 1));
}
return t;
}
/**
* 清空列表
*/
public void reset(){
word_Time_List.clear();
}
}
读取歌词时,原文是把歌词和时间文件分开成两个列表的,这样做,在歌词同步的时候挺麻烦的,特别是有些歌词,前面几行是作者、歌名、广告等,又没标注时间,导致两个列表长度也不一致,同步时添加了难度,所以我把他们合并在一起了。
调用方法:
/**
* 初始化歌词,调用LrcRead类
*
* @param LrcPath
* 歌词路径
*
* @return void
*/
public void initLrc(String LrcPath) {
try {
lrcHandler.reset(); // 先清空原有的数据
lrcHandler.readLRC(LrcPath);
word_Time_List = lrcHandler.getWordTimeList();
tvCurrentWord.setText("歌词准备完毕");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
歌词的同步
/**
* 开始或继续SeekBar
*
* @return void
* */
private void startSeekBar() {
seekBar.setEnabled(true);
try {
mRunnable = new Runnable() {
public void run() {
// 每3秒执行一次,执行进度条
mHandler.postDelayed(mRunnable, 100); // 给自己发送消息,自运行
int temp = mMediaPlayer01.getCurrentPosition();
seekBar.setProgress(temp);
String time = formatTime(temp); // 格式化时间
tvCurrentTime.setText(time);
//歌词同步
if (lrcExits) { // 歌词存在
// 防止下标越界
if (currentIndex != word_Time_List.size()) {
int lrcTime = Integer.parseInt(word_Time_List
.get(currentIndex).get("time").toString());
// 如果当前播放时间和歌词时间在一定范围,这里设置0到200毫秒,切换歌词
if (lrcTime >= temp && lrcTime <= temp + 200) {
String Currentword = word_Time_List
.get(currentIndex).get("word")
.toString();
String nextWord = null;
if (currentIndex + 1 != word_Time_List.size() - 1)
nextWord = word_Time_List
.get(currentIndex + 1).get("word")
.toString();
else
lrcExits = false;
tvCurrentWord.setText(Currentword);
tvNextWord.setText(nextWord);//写到textview上
currentIndex++;
if (currentIndex > word_Time_List.size() - 1)
lrcExits = false;
}
}
}
}
};
// 通过Handler启动线程
mHandler.post(mRunnable); // 发送消息,启动线程运行
} catch (Exception e) {
}
}
歌词的同步,获取刚刚解析到的歌词列表中的时间,与当前播放的时间比较,相差200ms,就把当前歌词和下一句替换上去两个textview。歌词同步还有一个方法比较重要的:
/**
* 在mTimeList中查找歌词序号
*
* @param targetTime
* 当前播放的时间
* @return index 当前时间附近的歌词序号
*/
public int findIndexOfTime(int targetTime) {
int index = 0;
int currentTime;
if (word_Time_List.size() <= 1)
return 1;
for (int i = 0; i < word_Time_List.size() - 1; i++) {
currentTime = Integer.parseInt(word_Time_List.get(i).get("time")
.toString());
if (targetTime <= currentTime) { //列表时间大于当前时间
index = i;
break;
}
}
return index;
}
该方法时遍历歌词列表的时间,如果列表时间大于当前播放时间,就把这个序号返回去,在拉动播放进度条时调用这个同步歌词,歌词需要下载时也调用,毕竟下载和处理歌词也是需要时间的,有一些歌曲一开始就播放了,所以需要跳过已经播放的歌词。
调用方法:
int temp = mMediaPlayer01.getCurrentPosition();
String time = formatTime(temp); //把获取到的时间转换一下,转换成毫秒的字符串
currentIndex = findIndexOfTime(temp); //currentIndex 为当前歌词列表的序号,int,全局变量
(函数非原创,忘了从哪里拿来改的了)
formatTime():
/**
* 时间转换
*
* @param ms
* 需要转化的时间,单位毫秒
* @return String 返回00:00格式的分秒时间
*/
public static String formatTime(int ms) {
int ss = 1000; // 秒
int mi = ss * 60; // 分
int minute = ms / mi;// 计算分
int second = (ms - minute * mi) / ss; // 计算秒
StringBuffer sb = new StringBuffer();
if (minute > 0) {
sb.append(minute + ":");
} else {
sb.append("00:");
}
if (second > 0 && second <= 9) {
sb.append("0" + second);
} else if (second > 9) {
sb.append(second);
} else {
sb.append("00");
}
return sb.toString();
}
歌词处理类来源:http://www.cnblogs.com/wenjiang/archive/2013/05/06/3063259.html