0

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;

    }
}
vvvvv
  • 25,404
  • 19
  • 49
  • 81
user3078608
  • 103
  • 1
  • 5
  • 15
  • If you only need to do it for one song, I would suggest making a video of the lyrics with the highlights appearing at the appropriate time, using a video editor. – DJClayworth Apr 28 '14 at 15:44
  • Are you using something like a [.srt](http://en.wikipedia.org/wiki/SubRip#SubRip_text_file_format) file format? – ggovan Apr 28 '14 at 16:10
  • @DJClayworth that would definitely be easier but I'm working on a final project for a programming class and would like to be able to accomplish this through coding – user3078608 Apr 28 '14 at 16:31
  • @ggovan nope, my program only accepts mp3 files. – user3078608 Apr 28 '14 at 16:32
  • So how do you know when to display what lyrics? – ggovan Apr 28 '14 at 16:33
  • Because I'm only trying to do this for one specific song, when the user clicks play on a song I'll check to see if the artist and song name matches up with the artist and song name of the specific song I want to add the lyrics/karaoke feature to. If it matches, then because those lyrics are already stored in my program, I'll have them displayed. – user3078608 Apr 28 '14 at 16:41

1 Answers1

2

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

  1. 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.

  2. 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().

NESPowerGlove
  • 5,496
  • 17
  • 28
  • Makes sense, but how can I add something in my program while the song is playing that'll check if(milliseconds>0 and milliseconds<1000) then show "jingle" ? – user3078608 Apr 28 '14 at 16:37
  • 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. – NESPowerGlove Apr 28 '14 at 16:43
  • 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(). – NESPowerGlove Apr 28 '14 at 16:46