JLayer - 暂停和恢复歌曲

7

我注意到很多关于使用JLayer暂停/恢复MP3的主题,为了帮助大家,我专门设计了一个完整的类!请查看下面的答案。

注意:这是为我的个人使用而做的,所以可能没有一些人希望的那么强大。但由于其简单性,进行简单修改并不难。


即使您只是为了分享代码而创建问题,问题也应该像问题一样。 - Aleksandr Kravets
第一句话解释了这个。 - Josh M
在我看来,你应该像真正的问题一样安排你的问题文本(包括问号等)。解释并不是必要的。当前的问题文本更适合作为一个答案。 - Aleksandr Kravets
1
如果你说很多人都有这方面的问题,那就意味着在Stack Overflow上有相关问题。为什么不把你的代码放在回答中呢? - Denis Tulskiy
你知道,Stack Overflow会通知用户有新的答案,所以发帖并不是不合逻辑的。发布30个答案可以让用户知道你已经找到了解决方案。至少你应该发布这个问题的链接而不是全部文本。 - Aleksandr Kravets
显示剩余7条评论
3个回答

11
一个非常简单的实现播放器暂停播放的方式。它通过使用一个独立的线程来播放流,并告诉播放器线程何时暂停和恢复。
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);
        }
    }

}

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;
}

}

关于用法:

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

然后,当您想要暂停它时:

player.pause();

...并且总结一下:

player.resume();

我希望我能通过这篇文章帮助很多人。

1
你没有“暂停”播放器,而是强制结束并创建一个新的。此外,它不会从停止的地方继续,而是稍后(取决于在停止时BufferedInputStream填充了多少字节)的任意数量。在“恢复”时,您将流定位到某个位置,而不是有效的帧头(jlayer通常可以处理,除非0xFFFx出现在流数据中,在这种情况下它会“崩溃”)。依赖FileInputStream.available()获取文件长度也非常值得怀疑......如果要播放其他内容而不是文件,则不太灵活。 - Durandal
有很多解决一个问题的方法,这是其中一种解决方案。我相信这是最简单的方法,我知道 .available() 方法返回流中剩余字节数的估计值,并且当我测试它时,它从暂停的位置恢复的时间不到半秒钟。显然,当你测试它时,你得到了不同的结果。 - Josh M
只要你意识到解决方案的局限性,那就没问题了。在大多数情况下,它应该能够完成工作。我只是评论了一下,因为你可能想改进我提到的问题(请不要把它当成个人攻击)。“目标”精度取决于为BufferedInputStream选择的缓冲区大小和流的比特率。如果代码的用户决定使用更大的缓冲区(比如256kb),它将跳过几秒钟。当然,你可以通过真正计算通过BufferedInputStream耗尽了多少字节来解决这个问题,但这会增加代码的复杂性。 - Durandal
我知道并且感谢您的批评,但我觉得这是实现非常不错的结果最简单的方法。当我看其他类似问题的答案时,没有一个显示任何代码,他们的过程似乎太复杂和困难了,大多数人都不会理解其中的任何内容。仅通过查看我的解决方案中的代码,它非常简单,没有任何人不应该理解的部分。 - Josh M
你好,很好的文章,我有一个问题: 当参数是-1时,你使用FIS.skip(pos)有什么作用?如果我将0或50放入参数中呢?谢谢! - Namor Alves

1
虽然这个问题已经有些年头了,但您应该注意到,此解决方案将无法与最新的JLayer版本和AdvancedPlayer一起使用!
AdvancedPlayer -> public boolean play(int frames) -> 条件语句
if(!ret) { .. } 

必须重新引入,否则播放一帧后播放将停止。

编辑:

似乎自Java 7以来,它们处理守护线程的方式防止了恢复工作。只需删除即可。

t.setDaemon(true);

为了让它再次正常工作!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接