Reputation: 103
I have created a media player in Java using JLayer, and it accepts mp3 files. I also have the lyrics to a specific song appear once the user plays that song, but now I want to somehow highlight or change the text color of the lyrics as they are heard in the song (like karaoke). I only need to do this for one song - and I'll have the lyrics already implemented in my program. I have already searched for how to do this but can't seem to find exactly what I'm looking for. Below I added the code to my class that plays the music file.
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;
}
}
Upvotes: 0
Views: 3731
Reputation: 5496
You won't be able to detect what words are being sung in the song (with ease at least), but if you have the lyrics of the song in a file, and you have the sound file of the song, then to me it sounds like you could just add more info to that lyrics file to create a map of when the words of the lyrics are being sung in the song.
For example, if I was to do this with the song Jingle Bells, I may have a tab separated file that contains the lyrics, where one line is one word, with a begin and end time relative to the start of the song in milliseconds.
jingle 0 1000
bells 1001 1500
jingle 1501 2500
bells 2501 3000
... and so on
Edit for explaining how to code up keeping track of how long a song has been playing.
Two methods to create an instance variable called say, totalTimeSongHasBeenPlaying
I'm not sure how you have abstracted out playing your sound files, but say that you abstracted that out to a Sound
class, then you could have three methods, sound.soundStarted
, sound.soundStopped
, sound.soundRestarted
, then at the start of playing the sound you can call soundStarted
which could grab a System.nanoTime
or a System.currentTimeMillis
and on soundStopped
, you could grab it again and take the difference and add it to totalTimeSongHasBeenPlaying
, and on soundRestart
you could set totalTimeSongHasBeenPlaying
to zero.
Do some math against frame position the sound you are currently playing is versus how many frames is in a second for that file. I don't know the exact libraries for JLayer
, it's been a while since I used it, but that method should also tell you how far along in the file you are.
After that, the Sound class could also then have a method such as currentWordBeingSung()
, which looks at totalTimeSongHasBeenPlaying
, and uses the lookup table you created off the lyrics file during construction and returns the specific word uniquely (may be duplicates). Your gui when you create it, say your JLyricsViewer
, can hold an instance to your Sound
object and you can use a SwingTimer
to repaint it every 50 ms or so, where in your paintComponent
method it looks at currentWordBeingSung()
.
Upvotes: 2