用java写一个音乐播放程序,同时显示歌词
目录
1.LRC歌词文件
2.解析歌词
2.1对歌词进行解析
2.2对时间标签解析
2.3对歌词文件拆分
2.4歌词排序
3.音乐接入
4.整个程序
4.1 Yun2_4
4.2 Info
4.3 Lyric
主题程序分为三部分{1.创建音乐播放窗口
2.计时器
3.对歌词进行处理
}
本程序运用了一个包,一个主程序,2个类进行处理歌词和时间
1.lrc歌词文件
在LRC歌词文本中,通常包含两类标签:时间标签和歌词文本标签。
- 时间标签:时间标签用于指定歌词的显示时间。它通常由方括号包围,并包含了歌词的起始时间和结束时间。时间标签的格式可以是分秒格式([mm:ss.xx])或者毫秒格式([mm:ss:xxx]),其中mm表示分钟,ss表示秒,xx或xxx表示毫秒。
示例:
[00:12.34]歌词内容 [01:23.45][02:34.56]歌词内容
- 歌词文本标签:歌词文本标签用于包含实际的歌词文本内容。它位于时间标签之后,直到下一个时间标签出现之前。歌词文本标签没有特定的格式要求,可以是任意文本内容。
示例:
[00:12.34]这是第一句歌词 [00:16.78]这是第二句歌词
通过使用这两类标签,LRC歌词文本可以指定每句歌词的显示时间,并且可以在指定的时间点显示相应的歌词内容。
2.解析歌词
这里以水星记歌词为例:
[ti:水星记]
[ar:郭顶]
[al:飞行器的执行周期]
[by:]
[offset:0]
[00:00.00]水星记 - 郭顶
[00:06.50]词:郭顶
[00:13.00]曲:郭顶
[00:19.50]着迷于你眼睛
[00:21.81]
[00:23.21]银河有迹可循
[00:25.34]
[00:26.46]穿过时间的缝隙
[00:29.28]
[00:30.04]它依然真实地
[00:33.05]
[00:33.76]吸引我轨迹
[00:36.53]
[00:40.74]这瞬眼的光景
[00:43.25]
[00:44.27]最亲密的距离
[00:46.85]
[00:47.77]沿着你皮肤纹理 走
[00:51.68]过曲折手臂
[00:54.34]
[00:55.26]做个梦给你
[00:57.53]
[00:58.88]做个梦给你
[01:01.55]
[01:03.97]等到看你银色满际
[01:06.56]
[01:07.37]等到分不清季节更替
[01:11.89]
[01:13.24]才敢说沉溺
[01:16.38]
[01:20.28]还要多远才能进入你的心
[01:26.50]
[01:27.47]还要多久才能和你接近
[01:33.61]
[01:34.63]咫尺远近却
[01:37.08]无法靠近的那个人
[01:42.30]也等着和你相遇
[01:47.91]
[01:49.33]环游的行星
[01:52.05]
[01:52.57]怎么可以
[01:55.47]
[01:56.43]拥有你
[01:58.29]
[02:14.03]这瞬眼的光景
[02:16.57]
[02:17.42]最亲密的距离
[02:19.98]
[02:20.93]沿着你皮肤纹理
[02:24.61]走过曲折手臂
[02:27.66]
[02:28.36]做个梦给你
[02:30.78]
[02:31.88]做个梦给你
[02:34.92]
[02:36.89]等到看你银色满际
[02:40.59]等到分不清季节更替
[02:44.96]
[02:46.17]才敢说沉溺
[02:53.44]还要多远才能进入你的心
[02:59.70]
[03:00.63]还要多久才能和你接近
[03:06.78]
[03:07.73]咫尺远近却
[03:09.86]无法靠近的那个人
[03:15.43]也等着和你相遇
[03:20.94]
[03:22.13]环游的行星
[03:25.04]
[03:25.69]怎么可以
[03:29.38]拥有你
[03:35.83]
[04:05.52]还要多远才能进入你的心
[04:11.17]
[04:12.23]还要多久才能和你接近
[04:18.33]
[04:19.35]咫尺远近却无法靠近的那个人
[04:26.99]要怎么探寻
[04:30.62]要多么幸运
[04:33.84]才敢让你发觉你并不孤寂
[04:40.08]
[04:40.81]当我还可以再跟你飞行
[04:46.88]
[04:47.99]环游是无趣
[04:50.87]
[04:51.64]至少可以
[04:54.94]
[04:55.54]陪着你
2.1对歌词进行解析
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
/*
* 使用try-with-resources语句,创建一个BufferedReader对象并初始化它
*BufferedReader将使用FileReader来读取指定文件的内容*/
String[] arrStr = new String[100];
int cnt = 0;//用于记录行数
String line;
while ((line = reader.readLine()) != null) {
arrStr[cnt] = line;
cnt++;
/*
* 通过循环reader.readLine()方法会读取文件的下一行,并将其存储在line变量中
* 如果读取的行不为空,则将其存储在arrStr数组的当前索引位置,然后索引++*/
}
String[] arr = new String[cnt]; //创建一个新的数组,大小为歌词拥有的行数
System.arraycopy(arrStr, 0, arr, 0, cnt);
/*
* System.arraycopy复制方法,复制一个新的数组去掉多余的空
* arrStr表示是原数组,0表示起始位置,arr表示复制的目标数组,0表示起始位置,cnt表示复制元素的个数*/
return arr;//返回值arr数组
} catch (IOException e) {
throw new RuntimeException(e);
}//错误处理方法
}
2.2对时间标签解析
private long calSeconds(String str) {
long minute;//分钟
double second;//秒钟
// 01:10.7
String[] arrStr = str.split(":");//分割分秒
minute = Long.parseLong(arrStr[0]);//使用Long.parseLong()方法将分钟部分的字符串转换为long类型的整数
second = Double.parseDouble(arrStr[1]);//使用Double.parseDouble()方法将秒钟部分的字符串转换为double类型的小数
return (long) ((minute * 60 + second) * 1000);
}
2.3对歌词文件拆分
public static Lyric[] lyricSplit(String str) {
String[] strArr = str.split("]");
int length = strArr.length - 1;// 使用split()方法将字符串str按照"]"进行拆分,得到一个字符串数组strArr。数组的长度减1得到变量length,表示有几个显示点。
String lyric = strArr[length];//从strArr数组中获取最后一个元素,即歌词内容,赋值给变量lyric
Lyric[] lyrics = new Lyric[length];//建一个长度为length的Lyric对象数组lyrics
for (int i = 0; i < length; i++) {
strArr[i] = strArr[i].substring(1);
lyrics[i] = new Lyric(lyric, strArr[i]);
/*用循环遍历strArr数组的前length个元素。
在每次循环中,将strArr[i]的第一个字符去掉,得到去掉显示点的时间字符串,
然后使用lyric和时间字符串创建一个Lyric对象,并将其赋值给lyrics数组的相应位置。
* */
}
return lyrics;//返回lyrics数组,其中包含了拆分后的歌词数据
}
2.4歌词排序
public static void sort() {
for (int i = 0; i < length - 1; i++) {//比较轮数
for (int j = 0; j < length - i - 1; j++) {//比较次数
if (lyrics[j].getTime() > lyrics[j + 1].getTime()) {//如果前面的歌词时间大于后面则调换顺序
Lyric lyric = lyrics[j];
lyrics[j] = lyrics[j + 1];
lyrics[j + 1] = lyric;
}
}
}
}
3.音乐接入
String musicFile = "D:\\作业素材\\郭顶 - 水星记.wav";
Clip clip = null;
try {
File file = new File(musicFile);
AudioInputStream audioStream = AudioSystem.getAudioInputStream(file);
clip = AudioSystem.getClip();
clip.open(audioStream);
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
e.printStackTrace();
}
String musicFile = "D:\\作业素材\\郭顶 - 水星记.wav";
歌曲文件的路径可以是绝对路径或者相对路径,音乐格式个别不能播放,wav。MP3
可以
4.整个程序
4.1 Yun2_4
package yinyue;
import javax.sound.sampled.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class Yun2_4 {
static Lyric[] lyrics = new Lyric[100];// 定义一个数组用于存储歌词
static int length = 0;// 实际歌词的元素个数
public static void main(String[] args) {
String cyanText="\u001B[36m";
String yellowText="\u001B[33m";
String resetText="\u001B[0m";
System.out.println("\u001B[32m"+"欢迎使用音乐播放器!");
System.out.println("\u001B[33m"+ "正在播放音乐:水星记 - 郭顶");//音乐名称
String musicFile = "D:\\作业素材\\郭顶 - 水星记.wav"; //歌曲文件的路径,绝对路径或者相对路径,音乐格式个别不能播放
Clip clip = null;//播放音乐的接口,初始化
try {
File file = new File(musicFile);
AudioInputStream audioStream = AudioSystem.getAudioInputStream(file);//通过AudioSystem.getAudioInputStream(file)方法获取音频输入流audioStream
clip = AudioSystem.getClip();//AudioSystem.getClip()用于获取一个clip对象
clip.open(audioStream);//准备播放音乐
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
e.printStackTrace();
}//处理音乐不能播放的情况
String[] arr = readFile("C:\\Users\\62626\\IdeaProjects\\HelloWorld\\untitled\\yinyue.txt");//歌词的路径,可以是相对路径或绝对路径
// 加工数据
processingData(arr);
//歌词排序
sort();
clip.start();//打开音乐播放
showLyric();//歌词显示
}
public static String[] readFile(String fileName) {
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
/*
* 使用try-with-resources语句,创建一个BufferedReader对象并初始化它
*BufferedReader将使用FileReader来读取指定文件的内容*/
String[] arrStr = new String[100];
int cnt = 0;//用于记录行数
String line;
while ((line = reader.readLine()) != null) {
arrStr[cnt] = line;
cnt++;
/*
* 通过循环reader.readLine()方法会读取文件的下一行,并将其存储在line变量中
* 如果读取的行不为空,则将其存储在arrStr数组的当前索引位置,然后索引++*/
}
String[] arr = new String[cnt]; //创建一个新的数组,大小为歌词拥有的行数
System.arraycopy(arrStr, 0, arr, 0, cnt);
/*
* System.arraycopy复制方法,复制一个新的数组去掉多余的空
* arrStr表示是原数组,0表示起始位置,arr表示复制的目标数组,0表示起始位置,cnt表示复制元素的个数*/
return arr;//返回值arr数组
} catch (IOException e) {
throw new RuntimeException(e);
}//错误处理方法
}
/*try {
Scanner sc = new Scanner(new File(fileName));
String[] arrStr = new String[100];
while (sc.hasNext()) {
String line = sc.nextLine();
arrStr[cnt] = line;
cnt++;
}
String[] arr = new String[cnt];
System.arraycopy(arrStr, 0, arr, 0, cnt);
sc.close();
return arr;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
*/
public static void processingData(String[] arr) {
for (String s : arr) {
Lyric[] tmp = lyricSplit(s);//将当前字符串s拆分为Lyric对象数组tmp
System.arraycopy(tmp, 0, lyrics, length, tmp.length);// System.arraycopy()方法将tmp数组中的元素复制到全局的lyrics数组
length += tmp.length;//更新歌词长度
}
}
// 歌词字符串拆分为Lyric对象数组,其中每个Lyric对象包含了歌词内容和对应的时间。
public static Lyric[] lyricSplit(String str) {
String[] strArr = str.split("]");
int length = strArr.length - 1;// 使用split()方法将字符串str按照"]"进行拆分,得到一个字符串数组strArr。数组的长度减1得到变量length,表示有几个显示点。
String lyric = strArr[length];//从strArr数组中获取最后一个元素,即歌词内容,赋值给变量lyric
Lyric[] lyrics = new Lyric[length];//建一个长度为length的Lyric对象数组lyrics
for (int i = 0; i < length; i++) {
strArr[i] = strArr[i].substring(1);
lyrics[i] = new Lyric(lyric, strArr[i]);
/*用循环遍历strArr数组的前length个元素。
在每次循环中,将strArr[i]的第一个字符去掉,得到去掉显示点的时间字符串,
然后使用lyric和时间字符串创建一个Lyric对象,并将其赋值给lyrics数组的相应位置。
* */
}
return lyrics;//返回lyrics数组,其中包含了拆分后的歌词数据
}
// 冒泡法排序歌词
public static void sort() {
for (int i = 0; i < length - 1; i++) {//比较轮数
for (int j = 0; j < length - i - 1; j++) {//比较次数
if (lyrics[j].getTime() > lyrics[j + 1].getTime()) {//如果前面的歌词时间大于后面则调换顺序
Lyric lyric = lyrics[j];
lyrics[j] = lyrics[j + 1];
lyrics[j + 1] = lyric;
}
}
}
}
public static void showLyric() {
long start = System.currentTimeMillis();//首先获取当前时间的毫秒数,并将其赋值给变量start作为起始时间
int cnt = 0;//追踪已经展示的歌词数量
System.out.println("\u001B[36m" + " ~~~~~~~~~~~~~~~~~~~~~~歌词信息~~~~~~~~~~~~~~~~~~~~~~~~~" + "\u001B[0m");
while (cnt < length) {//只要cnt小于歌词数组的长度,就执行以下操作
long current = System.currentTimeMillis() - start;//计算当前时间与起始时间的差值,并将其赋值给变量current,表示已经过去的时间
if (current > lyrics[cnt].getTime()) {//如果current大于当前歌词对象的时间,说明该歌词应该被展示
String lyric = lyrics[cnt].toString();//将当前歌词对象转换为字符串,并将其赋值给变量lyric
String formattedLyric = "\u001B[35m" + "\u001B[1m" + "\u001B[4m" + lyric + "\u001B[0m"; // 设置歌词颜色为红色 加粗 下划线
System.out.println("\u001B[34m" + " * " + "\u001B[0m" + formattedLyric);
cnt++;
}
}
System.out.println("播放结束了~~~!");
}
}
4.2 Info
package yinyue;
public class Info {
private final String info;
public Info(String str){
info=str;
}
//创建一个对象并初始化
public String toString() {
return info;
}
}
4.3 Lyric
package yinyue;
public class Lyric extends Info {
private String timeStr;
private long time;
// 第一个参数是歌词,第二个参数是时间字符串
public Lyric(String str, String timeStr) {
super(str);
this.timeStr = timeStr;
time = calSeconds(timeStr);
}
public String getTimeStr() {
return timeStr;
}
// 计算秒数 将 01:10.7 转为毫秒 (01*60+10.7)*1000
private long calSeconds(String str) {
long minute;//分钟
double second;//秒钟
// 01:10.7
String[] arrStr = str.split(":");//分割分秒
minute = Long.parseLong(arrStr[0]);//使用Long.parseLong()方法将分钟部分的字符串转换为long类型的整数
second = Double.parseDouble(arrStr[1]);//使用Double.parseDouble()方法将秒钟部分的字符串转换为double类型的小数
return (long) ((minute * 60 + second) * 1000);
}
public long getTime() {//获取时间
return time;
}
public String toString() {
return timeStr + " " + super.toString();//时间和字符串拼接后返回
}
}