7

I've noticed that a lot of topics were about pausing/resuming an MP3 using JLayer, so in order to help everybody, I've made a whole entire class designed just for that! Please see the answer below.

Note: This was for my personal use, so it may not be as robust as some people were hoping. But it is not that hard to make simple modifications, due to its simplicity.

Roman C
  • 49,761
  • 33
  • 66
  • 176
Josh M
  • 11,611
  • 7
  • 39
  • 49
  • Question should look like question even if you created it just to share your code. – Aleksandr Kravets Aug 21 '12 at 14:42
  • The first sentence explains that. – Josh M Aug 21 '12 at 14:43
  • IMO, you should arrange your question text like real question(with question sign and stuff =)). Explanations aren't necessary. Current question text is more suitable for an answer. – Aleksandr Kravets Aug 21 '12 at 14:49
  • Meh, I suppose I could do that. But I don't think it's really necessary considering that I was just making a note on the questions that people have been asking, similiar to this topic. – Josh M Aug 21 '12 at 14:55
  • Well, you can post your code as an answer for particular questions. Why create a new one? Only few people are looking for an answer and others just waiting for people like you to answer them. – Aleksandr Kravets Aug 21 '12 at 15:04
  • confused: JLayer - where is it used? Or a typo? – kleopatra Aug 21 '12 at 15:24
  • kleopatra: JLayer is a library made by JavaZoom which allows you to play MP3 files. and Aleksandr Kravets: I think it'd be more easier if I made a new topic rather than individually going on each question and posting the same answer for all of them. – Josh M Aug 21 '12 at 16:01
  • thanks - confused because core java has a component javax.swing.JLayer (as of jdk7) – kleopatra Aug 21 '12 at 16:19
  • All good. :P I made this because I found that a lot of people wanted to know how to pause and resume using JLayer (from looking at all the questions similar to this). This is not the best solution, as Durandel stated, but it is the most simplistic solution for achieving a very decent result. – Josh M Aug 21 '12 at 16:36
  • 1
    If you say many people have problems with it, it would mean there are SO questions about it. Why don't you put your code in answers to those questions? – Denis Tulskiy Aug 21 '12 at 18:23
  • Denis Tulskiy: That wouldn't be a good idea considering that most of those questions are inactive as of today, and it'd be very illogical to post the EXACT SAME answer on 30 different questions. It'd be much more convenient to post 1 answer for my own question so people could use it as a reference. – Josh M Aug 21 '12 at 18:36
  • You know, SO notifies user about new answers, so there's nothing illogical in posting. Posting 30 answers lets users know that you've found a solution. At least you should post a link to this question instead of full text. – Aleksandr Kravets Aug 26 '12 at 07:46

3 Answers3

10

A very simple implementation of a player that is really pausing playback. It works by using a separate thread to play the stream and telling the player thread if/when to pause and resume.

public class PausablePlayer {

    private final static int NOTSTARTED = 0;
    private final static int PLAYING = 1;
    private final static int PAUSED = 2;
    private final static int FINISHED = 3;

    // the player actually doing all the work
    private final Player player;

    // locking object used to communicate with player thread
    private final Object playerLock = new Object();

    // status variable what player thread is doing/supposed to do
    private int playerStatus = NOTSTARTED;

    public PausablePlayer(final InputStream inputStream) throws JavaLayerException {
        this.player = new Player(inputStream);
    }

    public PausablePlayer(final InputStream inputStream, final AudioDevice audioDevice) throws JavaLayerException {
        this.player = new Player(inputStream, audioDevice);
    }

    /**
     * Starts playback (resumes if paused)
     */
    public void play() throws JavaLayerException {
        synchronized (playerLock) {
            switch (playerStatus) {
                case NOTSTARTED:
                    final Runnable r = new Runnable() {
                        public void run() {
                            playInternal();
                        }
                    };
                    final Thread t = new Thread(r);
                    t.setDaemon(true);
                    t.setPriority(Thread.MAX_PRIORITY);
                    playerStatus = PLAYING;
                    t.start();
                    break;
                case PAUSED:
                    resume();
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * Pauses playback. Returns true if new state is PAUSED.
     */
    public boolean pause() {
        synchronized (playerLock) {
            if (playerStatus == PLAYING) {
                playerStatus = PAUSED;
            }
            return playerStatus == PAUSED;
        }
    }

    /**
     * Resumes playback. Returns true if the new state is PLAYING.
     */
    public boolean resume() {
        synchronized (playerLock) {
            if (playerStatus == PAUSED) {
                playerStatus = PLAYING;
                playerLock.notifyAll();
            }
            return playerStatus == PLAYING;
        }
    }

    /**
     * Stops playback. If not playing, does nothing
     */
    public void stop() {
        synchronized (playerLock) {
            playerStatus = FINISHED;
            playerLock.notifyAll();
        }
    }

    private void playInternal() {
        while (playerStatus != FINISHED) {
            try {
                if (!player.play(1)) {
                    break;
                }
            } catch (final JavaLayerException e) {
                break;
            }
            // check if paused or terminated
            synchronized (playerLock) {
                while (playerStatus == PAUSED) {
                    try {
                        playerLock.wait();
                    } catch (final InterruptedException e) {
                        // terminate player
                        break;
                    }
                }
            }
        }
        close();
    }

    /**
     * Closes the player, regardless of current state.
     */
    public void close() {
        synchronized (playerLock) {
            playerStatus = FINISHED;
        }
        try {
            player.close();
        } catch (final Exception e) {
            // ignore, we are terminating anyway
        }
    }

    // demo how to use
    public static void main(String[] argv) {
        try {
            FileInputStream input = new FileInputStream("myfile.mp3"); 
            PausablePlayer player = new PausablePlayer(input);

            // start playing
            player.play();

            // after 5 secs, pause
            Thread.sleep(5000);
            player.pause();     

            // after 5 secs, resume
            Thread.sleep(5000);
            player.resume();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

}
Durandal
  • 19,919
  • 4
  • 36
  • 70
3
import java.io.BufferedInputStream;
import java.io.FileInputStream;

import javax.swing.JOptionPane;

import javazoom.jl.player.Player;


public class CustomPlayer {

private Player player;
private FileInputStream FIS;
private BufferedInputStream BIS;
private boolean canResume;
private String path;
private int total;
private int stopped;
private boolean valid;

public CustomPlayer(){
    player = null;
    FIS = null;
    valid = false;
    BIS = null;
    path = null;
    total = 0;
    stopped = 0;
    canResume = false;
}

public boolean canResume(){
    return canResume;
}

public void setPath(String path){
    this.path = path;
}

public void pause(){
    try{
    stopped = FIS.available();
    player.close();
    FIS = null;
    BIS = null;
    player = null;
    if(valid) canResume = true;
    }catch(Exception e){

    }
}

public void resume(){
    if(!canResume) return;
    if(play(total-stopped)) canResume = false;
}

public boolean play(int pos){
    valid = true;
    canResume = false;
    try{
    FIS = new FileInputStream(path);
    total = FIS.available();
    if(pos > -1) FIS.skip(pos);
    BIS = new BufferedInputStream(FIS);
    player = new Player(BIS);
    new Thread(
            new Runnable(){
                public void run(){
                    try{
                        player.play();
                    }catch(Exception e){
                        JOptionPane.showMessageDialog(null, "Error playing mp3 file");
                        valid = false;
                    }
                }
            }
    ).start();
    }catch(Exception e){
        JOptionPane.showMessageDialog(null, "Error playing mp3 file");
        valid = false;
    }
    return valid;
}

}

And for the usage:

CustomPlayer player = new CustomPlayer();
player.setPath("MP3_FILE_PATH");
player.play(-1);

and then when you want to pause it:

player.pause();

...and to resume it:

player.resume();

I hope I've helped a lot of people with this.

Josh M
  • 11,611
  • 7
  • 39
  • 49
  • 1
    You are not *pausing* the player, you kill it and create a new one. Also it does not continue from the place where it stopped, but an arbitrary amount of bytes later (depending on how full the BufferedInputStream is when stopping). When 'resuming', you position the stream *somewhere*, not at a valid frame header (jlayer usually copes with that, unless 0xFFFx appears in the stream data, in that case it will *crash*). Relying on FileInputStream.available() to get the length of the file is also very questionable... and not very flexible, if you want to play something other than a file. – Durandal Aug 21 '12 at 14:46
  • There are plenty of solutions in order to solve 1 problem; this is a solution. I believe that this is the most simplistic way, and I'm aware that the .available() method returns an ESTIMATE of the amount of bytes left in the stream, and when I tested it, it resumed from literally less than a half a second from where it was paused at. You must have got different results when you tested it, clearly. – Josh M Aug 21 '12 at 14:51
  • Its fine as long as you're aware of the limitations of your solution. It should get the job done in most cases. I only commented because you may want to improve the issues I mentioned (please don't take it personally). The 'target' precision depends on the buffer size chosen for the BufferedInputStream and the streams bitrate. If the user of the code decided to use a larger buffer (lets say 256kb) it would skip several seconds. You *could* address this by really counting how may bytes have been exhausted through the BufferedInputStream, of course it increases the complexity of the code. – Durandal Aug 21 '12 at 15:05
  • I am aware and I do appreciate your criticism, but I feel like this is the most simple way to achieve a very decent result. When I was looking at the other answers to questions similar this one, none of which displayed any code, it seemed like their process was too complex and difficult, and majority of the people wouldn't understand what any of that means. Just by looking at the code in my solution, it's very simple, and there's not a section that nobody shouldn't understand. – Josh M Aug 21 '12 at 16:04
  • Hi, nice post, i have 1 question: What you use FIS.skip(pos) when parameters is -1? if i put 0 or 50 in parameters? Tankyou! – Namor Alves Sep 07 '12 at 22:40
1

though this question is some years old now, you should notice that this solution won't work with the newest JLayer version and the AdvancedPlayer!

AdvancedPlayer -> public boolean play(int frames) -> the conditional

if(!ret) { .. } 

has to be reintroduced, because otherwise the playback will stop after one frame played.

EDIT:

it seems that since Java 7, they handling of the daemonThreads prevents the resume from working. just remove the

t.setDaemon(true);

for it to work again!

Zahlii
  • 818
  • 1
  • 7
  • 17