JavaFX: Java音乐播放读取歌词
- 1、lrc歌词文件
- 2、解析lrc歌词
- 2.1 读取每行歌词
- 2.2 解析歌词时间标签Time-tag
- 2.3 解析歌词标识标签ID-tags
- 2.4 创建对象包含歌词相关信息
- 3、播放显示歌词
- ** 相关文献
JavaFX: Java音乐播放
1、lrc歌词文件
lrc歌词文件的扩展名
- 1、标准格式:
[分钟:秒.毫秒] 歌词
注释:(如图1所示)中括号、冒号、点号全都要求英文输入状态; - 2、其他格式①:
[分钟:秒] 歌词
- 3、其他格式②:
[分钟:秒:毫秒] 歌词
,与标准格式相比,秒后边的点号被改成了冒号。
lrc歌词文本中含有两类标签:
- 一是
标识标签
,其格式为“[标识名:值]”主要包含以下预定义的标签:
[ar:歌手名]、[ti:歌曲名]、[al:专辑名]、[by:编辑者(指lrc歌词的制作人)]、[offset:时间补偿值] (其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的,但多数的MP3可能不会支持这种标签)。- 二是
时间标签
,形式为“[mm:ss]”或“[mm:ss.ff]”(分钟数:秒数.百分之一秒数),时间标签需位于某行歌词中的句首部分,一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。当歌曲播放到达某一时间点时,MP3就会寻找对应的时间标签并显示标签后面的歌词文本,这样就完成了“歌词同步”的功能。
■ 时间标签(Time-tag)
形式为"[mm:ss]"(分钟数:秒数)或"[mm:ss.ff]"。数字须为非负整数, 比如"[12:34.50]"是有效的,而"[0x0C:-34.50]"无效(但也有不太规范的歌词采用[00:-0.12]的方式表示负值以显示歌曲名,部分播放器是支持的)。 它可以位于某行歌词中的任意位置。一行歌词可以包含多个时间标签(比如歌词中的迭句部分)。根据这些时间标签,用户端程序会按顺序依次高亮显示歌词,从而实现卡拉OK功能。另外,标签无须排序。
■ 标识标签(ID-tags)
其格式为"[标识名:值]"。大小写等价。以下是预定义的标签。
[ar:艺人名]
[ti:曲名]
[al:专辑名]
[by:编者(指编辑LRC歌词的人)]
[offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。
2、解析lrc歌词
[by:天空的星空_SJ5q]
[ti:黄昏]
[ar:周传雄]
[al:忘记]
[by:]
[offset:0]
[00:31.86]
[00:35.19]过完整个夏天忧伤并没有好一些
[00:38.43]
[00:41.29]开车行驶在公路无际无边
[00:47.38]有离开自己的感觉
[00:50.91]
[00:53.64]唱不完一首歌
[00:57.07]
[00:59.75]疲倦还剩下黑眼圈
[01:03.12]
[01:05.92]感情的世界伤害在所难免
[01:12.03]黄昏再美终要黑夜
[01:15.65]
[01:18.64]依然记得从你口中
[01:21.69]说出再见坚决如铁
[01:25.16]昏暗中有种烈日灼身的错觉
[01:30.48]黄昏的地平线
[01:33.53]划出一句离别
[01:36.65]爱情进入永夜
[01:40.30]
[01:43.23]依然记得从你眼中
[01:46.22]滑落的泪伤心欲绝
[01:49.67]混乱中有种热泪烧伤的错觉
[01:55.10]黄昏的地平线
[01:58.17]割断幸福喜悦
[02:01.34]相爱已经幻灭
[02:05.48]
[02:19.87]唱不完一首歌
[02:22.96]
[02:25.89]疲倦还剩下黑眼圈
[02:29.39]
[02:32.11]感情的世界伤害在所难免
[02:38.14]黄昏再美终要黑夜
[02:42.39]
[02:44.73]依然记得从你口中
[02:47.79]说出再见坚决如铁
[02:51.26]昏暗中有种烈日灼身的错觉
[02:56.65]黄昏的地平线
[02:59.72]划出一句离别
[03:02.79]爱情进入永夜
[03:06.38]
[03:09.46]依然记得从你眼中
[03:12.38]滑落的泪伤心欲绝
[03:15.83]混乱中有种热泪烧伤的错觉
[03:21.26]黄昏的地平线
[03:24.34]割断幸福喜悦
[03:27.42]相爱已经幻灭
[03:31.49]
[03:58.73]依然记得从你口中
[04:01.60]说出再见坚决如铁
[04:05.18]昏暗中有种烈日灼身的错觉
[04:10.49]黄昏的地平线
[04:13.56]划出一句离别
[04:16.64]爱情进入永夜
[04:20.10]
[04:23.44]依然记得从你眼中
[04:26.26]滑落的泪伤心欲绝
[04:29.71]混乱中有种热泪烧伤的错觉
[04:35.10]黄昏的地平线
[04:38.17]割断幸福喜悦
[04:41.23]相爱已经幻灭
2.1 读取每行歌词
这里使用commons-io
库 org.apache.commons.io.FileUtils
:
lineList = FileUtils.readLines(file, Charset.defaultCharset());
当然也可以自己实现:
InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);
BufferedReader bufferedReader = new BufferedReader(read);
String lineStr = null;
while ((lineStr = bufferedReader.readLine()) != null) {
// ... ...
}
read.close();
2.2 解析歌词时间标签Time-tag
private static Map<Long, String> parseLongTime(String line) {
String regex = "\\[(\\d{1,2}):(\\d{1,2}).(\\d{1,2})\\]";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(line);
Map<Long, String> map = null;
while (matcher.find()) {
map = new HashMap<Long, String>();
// [00:35.19] ----对应---> [分钟:秒.毫秒]
String min = matcher.group(1);
String sec = matcher.group(2);
String mill = matcher.group(3);
long time = getLongTime(min, sec, mill + "0");
String text = line.substring(matcher.end());
map.put(time, text);
}
return map;
}
private static long getLongTime(String min, String sec, String mill) {
int m = Integer.parseInt(min);
int s = Integer.parseInt(sec);
int ms = Integer.parseInt(mill);
long time = m * 60 * 1000 + s * 1000 + ms;
return time;
}
2.3 解析歌词标识标签ID-tags
/**
* [ar:艺人名]
* [ti:曲名]
* [al:专辑名]
* [by:编者(指编辑LRC歌词的人)]
* [offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。
*/
private static Map<String, String> parseIdTags(String line) {
String regex = "\\[(ar|ti|al|by|offset):(.*)\\]";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(line);
Map<String, String> map = null;
while (matcher.find()) {
map = new HashMap<String, String>();
String idTag = matcher.group(1);
String idStr = matcher.group(2);
XLog.i("matcher.groupCount() = " + matcher.groupCount() + "; idTag = " + idTag + "; idStr = " + idStr);
map.put(idTag, idStr);
}
return map;
}
2.4 创建对象包含歌词相关信息
重写toString()方法输出信息:
public class Lyric {
/**
* [ar:艺人名]
* [ti:曲名]
* [al:专辑名]
* [by:编者(指编辑LRC歌词的人)]
* [offset:时间补偿值] 其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。
*/
private enum idTag {
ar, ti, al, by, offset;
}
private String artist;
private String title;
private String album;
private String bySomebody;
private int offset;
// [00:35.19] ----对应---> [分钟:秒.毫秒]
private List<Map.Entry<Long, String>> lrcList;
public Lyric() {
lrcList = new ArrayList<>();
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAlbum() {
return album;
}
public void setAlbum(String album) {
this.album = album;
}
public String getBySomebody() {
return bySomebody;
}
public void setBySomebody(String bySomebody) {
this.bySomebody = bySomebody;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public List<Map.Entry<Long, String>> getLrcList() {
return lrcList;
}
public void setLrcList(List<Map.Entry<Long, String>> lrcList) {
this.lrcList = lrcList;
}
public void addLrcMap(Map.Entry<Long, String> map) {
lrcList.add(map);
}
public void set(Map.Entry<String, String> entry) {
XLog.i(entry.getKey() + " : " + entry.getValue());
String value = entry.getValue();
if (value == null || value.trim().length() <= 0) {
return;
}
String key = entry.getKey();
if (idTag.ar.toString().equals(key)) {
artist = value;
} else if (idTag.ti.toString().equals(key)) {
title = value;
} else if (idTag.al.toString().equals(key)) {
album = value;
} else if (idTag.by.toString().equals(key)) {
bySomebody = value;
} else if (idTag.offset.toString().equals(key)) {
offset = Integer.parseInt(value);
}
}
@Override
public String toString() {
String str = "Lyric {" +
"artist='" + artist + '\'' +
", title='" + title + '\'' +
", album='" + album + '\'' +
", bySomebody='" + bySomebody + '\'' +
", offset=" + offset +
", lrcList= {\n";
StringBuffer sb = new StringBuffer(str);
for (Map.Entry<Long, String> entry : lrcList) {
sb.append("\t" + entry.toString() + "\n");
}
sb.append("\t}\n");
return sb + "}";
}
}
3、播放显示歌词
JavaFx
中监听Service<Lyric>
设置text控件
public static class LyricService extends Service<Lyric> {
private static LyricService mInstance;
private String mPath;
private LyricService() {
}
public static LyricService getInstance() {
if (mInstance == null) {
synchronized (LyricService.class) {
if (mInstance == null) {
mInstance = new LyricService();
}
}
}
return mInstance;
}
@Override
protected Task<Lyric> createTask() {
return new Task<>() {
private Lyric mLyric;
@Override
protected Lyric call() {
mLyric = LrcParser.parse(mPath);
return mLyric;
}
};
}
public void startFindLyricService(AudioFile audioFile) {
String ext = audioFile.getExt();
String path = audioFile.getFile().getAbsolutePath();
path = path.substring(0, path.length() - ext.length()) + "lrc";
XLog.i(path);
this.startFindLyricService(path);
}
public void startFindLyricService(String path) {
mPath = path;
reset();
start();
}
}
** 相关文献
Java使用正则表达式解析LRC歌词文件 https://www.cnblogs.com/wuqianling/p/5656761.html
lrc歌词文件格式 https://blog.51cto.com/u_13488278/3209754