我使用JLayer在Java中创建了一个媒体播放器,它接受mp3文件。当用户播放某首歌时,我也会出现某首歌的歌词,但现在我想在歌曲中听到歌词时以某种方式突出显示或更改歌词的文本颜色(比如卡拉OK)。我只需要为一首歌做这件事——我会在我的程序中实现歌词。我已经搜索过如何做到这一点,但似乎找不到我想要的确切内容。下面,我将代码添加到我的类中,用于播放音乐文件。
public class PlayMusic {
/**
* Global variables. FileInputStream obtains input bytes from a file system
* and reads streas of raw bytes. BufferedInputStream adds functionality
* to the fis, and creates an internal buffer array.
*/
private String filename;
private Player player;
private boolean canResume;
private boolean valid;
private int total;
private int stopped;
FileInputStream fis;
BufferedInputStream bis;
/**
* Constructor the takes in the path of the mp3 file to be played.
* @param filename - path of the mp3 file
*/
public PlayMusic(String filename) {
this.filename = filename;
this.canResume = false;
this.valid = false;
this.total = 0;
this.stopped = 0;
this.fis = null;
this.bis = null;
}
/**
* Function called to stop a song altogether as opposed to pausing.
*/
public void close() {
if (player != null)
player.close();
stopped = 0;
fis = null;
bis = null;
player = null;
canResume = false;
}
/**
* Function called to pause a song. Fis.available() is a method that returns
* the number of remaining bytes that can be read from the input stream.
*/
public void pause(){
try {
if (fis!=null)
stopped = fis.available();
if (player!= null)
player.close();
fis = null;
bis = null;
player = null;
if(valid)
canResume = true;
} catch (IOException e) {
}
}
/**
* Function called when we want to resume a song from where it left off
* after being paused.
*/
public void resume()
{
if(!canResume)
return;
if(play(total-stopped))
canResume = false;
}
/**
* Function called to play the song and keep track of where in the song the
* user presses stop in order for the resume button to work properly. Fis.skip
* skips over and discards pos bytes of data from fis.
* @param pos - The position of the song in which we want to resume play
* @return
*/
public boolean play(int pos) {
valid = true;
canResume = false;
try {
fis = new FileInputStream(filename);
total = fis.available();
if(pos> -1)
fis.skip(pos);
bis = new BufferedInputStream(fis);
player = new Player(bis);
}
catch (Exception e) {
System.out.println("Problem playing file " + filename);
System.out.println(e);
}
/**
* Run the play button in a new thread so the music plays in the background.
*/
new Thread() {
public void run() {
try { player.play(); }
catch (Exception e) { System.out.println(e); valid = false; }
}
}.start();
return valid;
}
}
您将无法检测歌曲中正在演唱的单词(至少可以轻松地检测),但如果您在一个文件中有歌曲的歌词,并且您有歌曲的声音文件,那么对我来说,听起来您可以在歌词文件中添加更多信息,以创建歌曲中何时演唱歌词的映射。
例如,如果我用歌曲《铃儿响叮当》来做这件事,我可能会有一个以制表符分隔的文件,其中包含歌词,其中一行是一个单词,相对于歌曲开头的开始和结束时间以毫秒为单位。
jingle 0 1000
bells 1001 1500
jingle 1501 2500
bells 2501 3000
... and so on
编辑以解释如何编码以跟踪歌曲播放的时间。
创建名为totalTimeSongHasBeenPlaying
的实例变量的两种方法
我不确定你是如何抽象出播放声音文件的,但假设你把它抽象成一个
Sound
类,那么你可以有三个方法,sound.soundStarted
、sound.soundStopped
、sound.soundRestarted
,然后在播放声音开始时,你可以调用soundStarted
,它可以获取System.nanoTime
或System.currentTimeMillis
,在soundStopped
上,你可以再次获取它,并将差异添加到totalTimeSongHasBeenPlaying
,在CCD_ 11上可以将CCD_ 12设置为零。根据当前播放的声音的帧位置与该文件一秒钟内的帧数进行计算。我不知道
JLayer
的确切库,我已经有一段时间没有使用它了,但该方法也应该告诉你在文件中的进展情况。
之后,Sound类还可以有一个方法,如currentWordBeingSung()
,它查看totalTimeSongHasBeenPlaying
,并使用您在构建过程中从歌词文件中创建的查找表,并唯一地返回特定单词(可能是重复的)。当您创建gui时,比如说JLyricsViewer
,它可以保存Sound
对象的一个实例,并且您可以使用SwingTimer
每隔50毫秒左右重新绘制一次,在paintComponent
方法中,它看起来是currentWordBeingSung()
。