JSlider 问题音频播放器

Posted

技术标签:

【中文标题】JSlider 问题音频播放器【英文标题】:JSlider issue audio player 【发布时间】:2014-11-13 09:19:36 【问题描述】:

我正在尝试使用集成的 JSlider 构建一个音频播放器,它每微秒更新一次界面。

为此,我使用以下内容:

sliderTime.setMinimum(0);
sliderTime.setMaximum((int) audioClip.getMicrosecondPosition(););

我觉得这不是目前最好的实现(非常感谢任何改进它的建议)

顺便说一句,我面临的问题是 JSlider 第一秒没有更新。

请在下方找到 MCVE: 它只播放 wav 未压缩文件

主要

public class Main

    public static void main(final String[] args) 
    
        SwingUtilities.invokeLater(new Runnable() 
        
             @Override
             public void run() 
                       
                 JFrame f = new JFrame();
                 PlayerView pw = new PlayerView();
                 Border border = new EmptyBorder(15,15,15,15);
                 pw.setBorder(border);
                 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                 f.getContentPane().setLayout(new BorderLayout());
                 f.getContentPane().add(pw, BorderLayout.CENTER);
                 f.pack();
                 f.setLocationRelativeTo(null);
                 f.setVisible(true);
             
        );
    

音频播放器

public class AudioPlayer implements LineListener 

    private SimpleDateFormat dateFormater = new SimpleDateFormat("HH:mm:ss.SSS");   
    private TimeZone timeZone = Calendar.getInstance().getTimeZone();  

    public static final int REWIND_IN_MICROSECONDS = 3000000;
    public static final int FORWARD_IN_MICROSECONDS = 3000000;

    private boolean playCompleted;
    private boolean isStopped;
    private boolean isPaused;
    private boolean isRewinded;
    private boolean isForwarded;

    private Clip audioClip;

    public Clip getAudioClip() 
    
        return audioClip;
       

    public void load(String audioFilePath) throws UnsupportedAudioFileException, IOException, LineUnavailableException 
    
        File encodedFile = new File(audioFilePath);
        AudioInputStream pcmStream = Audiosystem.getAudioInputStream(encodedFile);
        AudioFormat format =pcmStream.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, format);
        audioClip = (Clip) AudioSystem.getLine(info);
        audioClip.addLineListener(this);
        audioClip.open(pcmStream);
    

    public long getClipMicroSecondLength() 
    
        return audioClip.getMicrosecondLength();
    

    public long getClipMicroSecondPosition() 
    
        return audioClip.getMicrosecondPosition();
    

    public String getClipLengthString() 
       
        long yourmilliseconds = audioClip.getMicrosecondLength() / 1_000;
        Date resultdate = new Date(yourmilliseconds);
        dateFormater.setTimeZone(TimeZone.getTimeZone(timeZone.getDisplayName(false, TimeZone.SHORT))); 
        return dateFormater.format(resultdate);
    

    public void play() throws IOException 
    
        audioClip.start();

        playCompleted = false;
        isStopped = false;

        while (!playCompleted) 
        
            try 
            
                Thread.sleep(30);
             
            catch (InterruptedException ex) 
            
                if (isStopped)
                
                    audioClip.stop();
                    break;
                
                else if (isPaused) 
                
                    audioClip.stop();
                 
                else if (isRewinded) 
                
                    if( audioClip.getMicrosecondPosition() <= REWIND_IN_MICROSECONDS)
                    
                        audioClip.setMicrosecondPosition(0);
                        isRewinded =false;
                    
                    else
                    
                        audioClip.setMicrosecondPosition(audioClip.getMicrosecondPosition() - REWIND_IN_MICROSECONDS);
                        isRewinded =false;
                    
                
                else if (isForwarded) 
                
                    if((audioClip.getMicrosecondLength() - audioClip.getMicrosecondPosition()) >= FORWARD_IN_MICROSECONDS)
                    
                        audioClip.setMicrosecondPosition(audioClip.getMicrosecondPosition() + FORWARD_IN_MICROSECONDS);
                        isForwarded =false;
                    
                    else
                    
                        audioClip.stop();
                        isForwarded =false;
                    
                 
                else 
                
                    audioClip.start();
                
            
        
        audioClip.close();
    

    public void stop() 
    
        isStopped = true;
    

    public void pause() 
    
        isPaused = true;
    

    public void resume() 
    
        isPaused = false;
    

    public void rewind() 
    
        isRewinded = true;
    

    public void forward() 
    
        isForwarded = true;
    

    @Override
    public void update(LineEvent event) 
    
        Type type = event.getType();
        if (type == Type.STOP) 
        
            if (isStopped || !isPaused) 
            
                playCompleted = true;
            
        
    

播放计时器

public class PlayingTimer extends Thread 

    private SimpleDateFormat dateFormater = new SimpleDateFormat("HH:mm:ss.SSS");   
    private TimeZone timeZone = Calendar.getInstance().getTimeZone();  

    private boolean isRunning = false;
    private boolean isPause = false;
    private boolean isReset = false;
    private boolean isRewinded = false;
    private boolean isForwarded = false;

    private long startTime;
    private long pauseTime;
    private long rewindTime;
    private long forwardTime;

    private JLabel labelRecordTime;
    private JSlider slider;
    private Clip audioClip;

    public void setAudioClip(Clip audioClip) 
    
        this.audioClip = audioClip;
    

    public PlayingTimer(JLabel labelRecordTime, JSlider slider) 
    
        this.labelRecordTime = labelRecordTime;
        this.slider = slider;
        dateFormater.setTimeZone(TimeZone.getTimeZone(timeZone.getDisplayName(false, TimeZone.SHORT))); 
    

    public void run() 
    
        isRunning = true;
        startTime = System.currentTimeMillis();

        while (isRunning) 
        
            try 
            
                Thread.sleep(30);
                if (!isPause) 
                
                    if (audioClip != null && audioClip.isRunning()) 
                    
                         long currentMicros = audioClip.getMicrosecondPosition();

                            // Compute the progress as a value between 0.0 and 1.0
                            double progress = 
                                (double)currentMicros / audioClip.getMicrosecondLength();

                            // Compute the slider value to indicate the progress
                            final int sliderValue = (int)(progress * slider.getMaximum());


                            // Update the slider with the new value, on the Event Dispatch Thread
                            SwingUtilities.invokeLater(new Runnable()
                            
                                @Override
                                public void run()
                                
                                    labelRecordTime.setText(toTimeString());
                                    slider.setValue(sliderValue);
                                
                            );
                    
                
                else 
                
                    pauseTime += 30;
                
            
            catch (InterruptedException ex) 
            
                if (isReset) 
                
                    slider.setValue(0);
                    labelRecordTime.setText("00:00:00.000");
                    isRunning = false;      
                    break;
                
                if (isRewinded) 
                
                    if( audioClip.getMicrosecondPosition() <= AudioPlayer.REWIND_IN_MICROSECONDS)
                    
                        //go back to start
                        rewindTime += audioClip.getMicrosecondPosition() / 1_000;
                    
                    else
                    
                        rewindTime += 3000;
                    
                    isRewinded =false;
                
                if (isForwarded) 
                
                    if((audioClip.getMicrosecondLength()- audioClip.getMicrosecondPosition()) <= AudioPlayer.FORWARD_IN_MICROSECONDS)
                    
                        forwardTime -= (audioClip.getMicrosecondLength()- audioClip.getMicrosecondPosition())/1_000; 
                    
                    else
                                       
                        forwardTime -= 3000;
                    
                    isForwarded=false;
                
            
         
    

    public void reset() 
    
        isReset = true;
        isRunning = false;
    

    public void rewind() 
    
        isRewinded = true;
    

    public void forward() 
    
        isForwarded = true;
    

    public void pauseTimer() 
    
        isPause = true;
    

    public void resumeTimer() 
    
        isPause = false;
    

    private String toTimeString() 
    
        long now = System.currentTimeMillis();
        Date resultdate = new Date(now - startTime - pauseTime - rewindTime - forwardTime);
        return dateFormater.format(resultdate);
    


播放器视图

 public class PlayerView extends JPanel implements ActionListener 
   

        private static final int BUTTON_HEIGTH =60; 
        private static final int BUTTON_WIDTH =120; 

        private AudioPlayer player = new AudioPlayer();
        private Thread playbackThread;
        private PlayingTimer timer;

        private boolean isPlaying = false;
        private boolean isPause = false;

        private String audioFilePath;
        private String lastOpenPath;

        private JLabel labelFileName;
        private JLabel labelTimeCounter;
        private JLabel labelDuration;

        private JButton buttonOpen;
        private JButton buttonPlay;
        private JButton buttonPause;
        private JButton buttonRewind;
        private JButton buttonForward;

        private JSlider sliderTime;

        private Dimension buttonDimension = new Dimension(BUTTON_WIDTH,BUTTON_HEIGTH);

        public PlayerView() 
        
            setLayout(new BorderLayout());
            labelFileName = new JLabel("File Loaded:");

            labelTimeCounter = new JLabel("00:00:00.000");
            labelDuration = new JLabel("00:00:00.000");

            sliderTime = new JSlider(0, 1000, 0);;
            sliderTime.setValue(0);
            sliderTime.setEnabled(false);

            buttonOpen   = new JButton("Open");
            buttonOpen.setPreferredSize(buttonDimension);
            buttonOpen.addActionListener(this);

            buttonPlay   = new JButton("Play");

            buttonPlay.setEnabled(false);
            buttonPlay.setPreferredSize(buttonDimension);
            buttonPlay.addActionListener(this);

            buttonPause  = new JButton("Pause");
            buttonPause.setEnabled(false);
            buttonPause.setPreferredSize(buttonDimension);
            buttonPause.addActionListener(this);

            buttonRewind = new JButton("Rewind");
            buttonRewind.setEnabled(false);
            buttonRewind.setPreferredSize(buttonDimension);
            buttonRewind.addActionListener(this);

            buttonForward= new JButton("Forward");
            buttonForward.setEnabled(false);
            buttonForward.setPreferredSize(buttonDimension);
            buttonForward.addActionListener(this);

            init();

        

        public void enableButtonPlay()
        
            buttonPlay.setEnabled(true);
        

        @Override
        public void actionPerformed(ActionEvent event) 
        
            Object source = event.getSource();
            if (source instanceof JButton) 
            
                JButton button = (JButton) source;
                if (button == buttonOpen) 
                
                    openFile();
                 
                else if (button == buttonPlay) 
                
                    if (!isPlaying) 
                    
                        playBack();
                     
                    else 
                    
                        stopPlaying();
                    
                 
                else if (button == buttonPause) 
                
                    if (!isPause) 
                    
                        pausePlaying();
                     
                    else 
                    
                        resumePlaying();
                    
                
                else if (button == buttonRewind) 
                
                    if (!isPause) 
                    
                        rewind();                   
                     
                
                else if (button == buttonForward) 
                
                    if (!isPause) 
                    
                        forward();
                     
                
            
        

        public void openFile(String path) 
        
            audioFilePath = path ;

            if (isPlaying || isPause) 
            
                stopPlaying();
                while (player.getAudioClip().isRunning()) 
                
                    try 
                    
                        Thread.sleep(100);
                     
                    catch (InterruptedException ex) 
                    
                        ex.printStackTrace();
                    
                
            
            playBack();
        

        private void openFile() 
        
            JFileChooser fileChooser = null;

            if (lastOpenPath != null && !lastOpenPath.equals("")) 
            
                fileChooser = new JFileChooser(lastOpenPath);
             
            else 
            
                fileChooser = new JFileChooser();
            

            FileFilter wavFilter = new FileFilter() 
            
                @Override
                public String getDescription() 
                
                    return "Sound file (*.WAV)";
                

                @Override
                public boolean accept(File file) 
                
                    if (file.isDirectory()) 
                    
                        return true;
                     
                    else 
                    
                        return file.getName().toLowerCase().endsWith(".wav");
                    
                
            ;

            fileChooser.setFileFilter(wavFilter);
            fileChooser.setDialogTitle("Open Audio File");
            fileChooser.setAcceptAllFileFilterUsed(false);

            int userChoice = fileChooser.showOpenDialog(this);
            if (userChoice == JFileChooser.APPROVE_OPTION) 
            
                audioFilePath = fileChooser.getSelectedFile().getAbsolutePath();
                lastOpenPath = fileChooser.getSelectedFile().getParent();

                if (isPlaying || isPause) 
                
                    stopPlaying();
                    while (player.getAudioClip().isRunning()) 
                    
                        try 
                        
                            Thread.sleep(100);
                         
                        catch (InterruptedException ex) 
                        
                            ex.printStackTrace();
                        
                    
                
                playBack();
            
        


        private void playBack() 
        
            timer = new PlayingTimer(labelTimeCounter, sliderTime);

            timer.start();
            isPlaying = true;

            playbackThread = new Thread(new Runnable() 
            
                @Override
                public void run() 
                
                    try 
                    
                        buttonPlay.setText("Stop");
                        buttonPlay.setEnabled(true);

                        buttonRewind.setEnabled(true);
                        buttonForward.setEnabled(true);

                        buttonPause.setText("Pause");
                        buttonPause.setEnabled(true);

                        player.load(audioFilePath);

                        timer.setAudioClip(player.getAudioClip());

                        labelFileName.setText("Playing File: " + ((File)new File(audioFilePath)).getName());

                        sliderTime.setMinimum(0);
                        sliderTime.setMaximum((int)player.getClipMicroSecondLength());

                        labelDuration.setText(player.getClipLengthString());

                        player.play();
                        labelFileName.setText("File Loaded: " + ((File)new File(audioFilePath)).getName());
                        resetControls();

                     
                    catch (UnsupportedAudioFileException ex) 
                    
                        JOptionPane.showMessageDialog(
                            PlayerView.this,  
                            "The audio format is unsupported!", 
                            "Error", 
                            JOptionPane.ERROR_MESSAGE);
                        resetControls();
                     
                    catch (LineUnavailableException ex) 
                    
                        JOptionPane.showMessageDialog(
                            PlayerView.this,  
                            "Could not play the audio file because line is unavailable!", 
                            "Error", 
                            JOptionPane.ERROR_MESSAGE);
                        resetControls();
                     
                    catch (IOException ex) 
                    
                        JOptionPane.showMessageDialog(
                            PlayerView.this,  
                            "I/O error while playing the audio file!", 
                            "Error", 
                            JOptionPane.ERROR_MESSAGE);
                        resetControls();
                    
                
            );

            playbackThread.start();
        

        private void stopPlaying() 
        
            isPause = false;

            buttonPause.setText(" Pause ");
            buttonPause.setEnabled(false);
            buttonRewind.setEnabled(false);
            buttonForward.setEnabled(false);

            timer.reset();
            timer.interrupt();

            player.stop();
            playbackThread.interrupt();
        

        private void pausePlaying() 
        
            labelFileName.setText("File Loaded: " + ((File)new File(audioFilePath)).getName());
            buttonRewind.setEnabled(false);
            buttonForward.setEnabled(false);

            buttonPause.setText("Resume");
            isPause = true;

            player.pause();
            timer.pauseTimer();

            playbackThread.interrupt();
        

        private void resumePlaying() 
        
            labelFileName.setText("Playing File: " +  ((File)new File(audioFilePath)).getName());
            buttonPause.setText(" Pause ");
            buttonRewind.setEnabled(true);
            buttonForward.setEnabled(true);
            isPause = false;

            player.resume();
            timer.resumeTimer();

            playbackThread.interrupt();     
        

        private void rewind() 
        
            player.rewind();
            timer.rewind();
            timer.interrupt();
            playbackThread.interrupt(); 
        

        private void forward() 
        
            player.forward();
            timer.forward();
            timer.interrupt();
            playbackThread.interrupt(); 
        

        private void resetControls() 
        
            timer.reset();
            timer.interrupt();
            isPlaying = false;  

            buttonPlay.setText("Play");

            buttonPause.setEnabled(false);
            buttonRewind.setEnabled(false);
            buttonForward.setEnabled(false);    
        

        private void init()
        


            add(labelFileName, BorderLayout.NORTH);
            add(labelTimeCounter, BorderLayout.WEST);
            add(labelDuration, BorderLayout.EAST);
            add(sliderTime, BorderLayout.CENTER);

            JPanel buttonContainer =new JPanel();
            add(buttonContainer, BorderLayout.SOUTH);

            buttonContainer.add(buttonOpen);
            buttonContainer.add(buttonPlay);
            buttonContainer.add(buttonPause);
            buttonContainer.add(buttonRewind);
            buttonContainer.add(buttonForward);

        

【问题讨论】:

实际上,slider.setValue 调用应该在 Event Dispatch Thread 上完成(将其包装到 SwingUtilities.invokeLater 调用中)。但是,这不应导致此问题。 ***.com/help/mcve 会很好。但在这种情况下,一种通用策略与 Zoran 建议的类似:将滑块定义为具有“合理”间隔,并将实际间隔按比例映射到滑块间隔。 谢谢,Jslider 总是使用 SwingUtilities.invokeLater 调用重新绘制 :) 明确一点:setValue 调用修改了模型,即 Swing 组件的状态。所以“setValue”调用应该在事件调度线程上完成。 顺便说一句,您发布的代码不是 MCVE。 MCVE 是仅用于演示问题的最小程序。当我们要求 MCVE 时,我们不仅仅指“您的代码”。 “架构如何影响播放音频?” 声音播放是特定于平台的,因此像 Clip 这样的类可能在每个平台上实现不同。因此,它们的行为可能因平台而异。 【参考方案1】:

好的,那么,Clip 的问题。这是一个 MCVE,从您描述问题的方式来看,它可能会重现它:

class TestFramePosition 
    public static void main(String[] a) throws Exception 
        File file = new File(a.length > 0 ? a[0] : "path/to/file.extension");
        AudioInputStream ais = AudioSystem.getAudioInputStream(file);
        final Clip clip = AudioSystem.getClip();

        clip.open(ais);
        clip.start();

        new Thread(new Runnable() 
            @Override
            public void run() 
                while(clip.isRunning()) 
                    try 
                        System.out.println(clip.getMicrosecondPosition());
                        Thread.sleep(1000 / 10);
                     catch(InterruptedException ignored) 
                
            
        ).start();

        System.in.read();
        System.exit(0);
    

无法在 OSX 10.6.8 和 Windows XP 上重现它,但您可以运行该代码以查看它是否可以在您的特定平台上运行。

所以,这里的问题是,正如我在 cmets 中所说,由于声音播放依赖于特定于平台的东西,所以像 Clip 这样的类将有不同的实现。它们的行为会略有不同。

例如,我发现当一个 Clip 播放完毕后,我的 Mac 电脑上的 Clip (a com.sun.media.sound.MixerClip) 返回 0 作为位置,而我的 Windows 电脑上的 Clip (a com.sun.media.sound.DirectAudioDevice$DirectClip) 返回最大值职位的价值。只是另一个以不同方式编程实现的小例子。

问题在于these methods 的合同定义有点模糊,但具体而言,它是由“自打开以来由该行捕获或渲染的样本帧数”定义的。这意味着它可能无法准确表示播放位置,而是读取和写入的数据量。

我昨天确实花了一段时间仔细阅读 JDK 源代码,但我找不到任何可以指向您所看到的行为的东西。

无论如何,归根结底是您是否可以接受平台与平台之间的轻微异常行为差异。您所看到的可能是一个错误,如果上述 MCVE 重现它,您可以报告它;但是我个人不希望它及时得到修复,因为这是 JDK 中没有引起太多关注的部分。它也逐渐被JavaFX所取代。

其他一些事情:

您在没有同步的线程之间共享状态。这会导致内存错误。您应该阅读concurrency tutorials,特别是synchronization。 在使用 Swing 时,您应该始终限制帧速率。 Swing 不会以 1000FPS 的速度绘制,它会积极地合并重绘。以这种速度更新滑块只会淹没 EDT。

您可以使用 SourceDataLine,因为它可以让您更好地控制缓冲行为。缺点是您必须基本上重新实现 Clip 的功能。

这是一个 MCVE,演示了一个播放循环来为 JSlider 供电。

这个例子不演示寻找。此外,由于 AudioInputStream 通常不支持标记操作,因此向后查找有点麻烦。向后搜索过程是:

停止当前播放并将其丢弃。 创建一个新的 AudioInputStream 并向前搜索。 开始新的播放。

另外,如果您打算使用 JSlider 进行搜索,您可能会遇到这样一个问题:在 JSlider 上调用 setValue 会导致它触发 ChangeEvent。所以你不能以编程方式更新滑块的值,也不能在不重新调整的情况下听它。这本身就是一个问答环节,所以如果您遇到这个问题,我建议您提出一个新问题。

import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.event.*;

import java.awt.Dimension;
import java.awt.BorderLayout;
import java.io.File;
import java.io.IOException;

public class PlaybackSlider implements Runnable, ActionListener 
    public static void main(String[] args) 
        SwingUtilities.invokeLater(new PlaybackSlider());
    

    JButton open;
    JButton play;
    JSlider slider;
    JLabel label;

    File file;
    PlaybackLoop player;

    @Override
    public void run() 
        JFrame frame = new JFrame("Playback Slider");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel content = new JPanel(new BorderLayout()) 
            @Override
            public Dimension getPreferredSize() 
                Dimension pref = super.getPreferredSize();
                pref.width = 480;
                return pref;
            
        ;

        slider = new JSlider(JSlider.HORIZONTAL, 0, 1000, 0);
        content.add(slider, BorderLayout.CENTER);

        JToolBar bar = new JToolBar(JToolBar.HORIZONTAL);
        bar.setFloatable(false);
        content.add(bar, BorderLayout.SOUTH);

        open = new JButton("Open");
        play = new JButton("Play");

        open.addActionListener(this);
        play.addActionListener(this);

        label = new JLabel("");

        bar.add(open);
        bar.add(new JLabel(" "));
        bar.add(play);
        bar.add(new JLabel(" "));
        bar.add(label);

        frame.setContentPane(content);
        frame.pack();
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    

    @Override
    public void actionPerformed(ActionEvent ae) 
        Object source = ae.getSource();

        if(source == open) 
            File f = getFile();
            if(f != null) 
                file = f;
                label.setText(file.getName());
                stop();
            
        

        if(source == play) 
            if(file != null) 
                if(player != null) 
                    stop();
                 else 
                    start();
                
            
        
    

    File getFile() 
        JFileChooser diag = new JFileChooser();
        int choice = diag.showOpenDialog(null);

        if(choice == JFileChooser.APPROVE_OPTION) 
            return diag.getSelectedFile();
         else 
            return null;
        
    

    void start() 
        try 
            player = new PlaybackLoop(file);
            new Thread(player).start();
            play.setText("Stop");
         catch(Exception e) 
            player = null;
            showError("the file couldn't be played", e);
        
    

    void stop() 
        if(player != null) 
            player.stop();
        
    

    void showError(String msg, Throwable cause) 
        JOptionPane.showMessageDialog(null,
            "There was an error because " + msg +
            (cause == null ? "." : "\n(" + cause + ").")
        );
    

    class PlaybackLoop implements Runnable 
        AudioInputStream in;
        SourceDataLine line;
        AudioFormat fmt;
        int bufferSize;

        boolean stopped;

        PlaybackLoop(File file) throws Exception 
            try 
                in = AudioSystem.getAudioInputStream(file);
                fmt = in.getFormat();

                bufferSize = (int)(fmt.getFrameSize() * (fmt.getSampleRate() / 15));

                line = AudioSystem.getSourceDataLine(fmt);
                line.open(fmt, bufferSize);
             catch(Exception e) 
                if(in != null)
                    in.close();
                if(line != null)
                    line.close();
                throw e;
            
        

        void stop() 
            synchronized(this) 
                this.stopped = true;
            
        

        @Override
        public void run() 
            line.start();
            byte[] buf = new byte[bufferSize];

            try 
                try 
                    int b;
                    long elapsed = 0;
                    long total = in.getFrameLength();

                    for(;;) 
                        synchronized(this) 
                            if(stopped) 
                                break;
                            
                        

                        b = in.read(buf, 0, buf.length);
                        if(b < 0) 
                            break;
                        

                        elapsed += b / fmt.getFrameSize();
                        updateSlider(elapsed, total);

                        line.write(buf, 0, b);
                    
                 finally 
                    line.close();
                    in.close();
                
             catch(IOException e) 
                e.printStackTrace(System.err);
                showError("there was a problem during playback", e);
            

            endOnEDT();
        

        void updateSlider(double elapsed, double total) 
            final double amt = elapsed / total;
            SwingUtilities.invokeLater(new Runnable() 
                @Override
                public void run() 
                    slider.setValue((int)Math.round(slider.getMaximum() * amt));
                
            );
        

        void endOnEDT() 
            SwingUtilities.invokeLater(new Runnable() 
                @Override
                public void run() 
                    player = null;
                    slider.setValue(0);
                    play.setText("Play");
                
            );
        
    

【讨论】:

【参考方案2】:

我假设您希望将 JSlider 用作进度条,并且在您设置最大值时,当前位置位于音频剪辑的末尾。 (您是在处理 Clip 还是 AudioClip?AudioClip 无法读取其位置 AFAIK。)如果您使用 Clip,使用 audioClip.getMicrosecondLength() 设置最大值会更安全。

由于音频必须在与更新 JSlider 的线程不同的线程上播放,我建议将您的 audioClip 设为 volatile 变量。这可能有助于解决有时发生的跨线程怪异现象。

Thread.sleep(1) 最多只能每毫秒更新一次。在某些系统(较旧的 Windows)上,该方法对系统时钟的依赖意味着实际更新的速度相差 16 毫秒。但是以超过 60 fps 的速度更新 JSlider 可能没有实际意义。屏幕监视器通常设置为 60Hz,人眼只能看到这么多。

此外,就时间而言,耳朵只能辨别这么多。例如,如果相差不到几毫秒,就很难判断两个敲击事件是否同时发生。

【讨论】:

谢谢,这个答案有很多建议 我的意思是添加另一个问题:JVM 不会实时处理声音。相反,它在线程之间切换时以“切片”的形式进行。所以,它会比实际播放提前一点,切换到其他东西,比如你的滑块,然后回去做更多的音频。这会使您的进度条有点跳跃,因为在处理音频时它没有被更新,反之亦然。当人们尝试读取鼠标位置(例如 Theremin)并获得平滑的音高更新时,效果会更加明显和烦人。但对于进度条,不需要那么准确。【参考方案3】:

您的代码存在几个问题。

正如 Phil Freihofner 所指出的,sleep(1) 以及对 isRunningisPause 字段的处理看起来非常可疑。在某种程度上,这与你的实际问题无关,但在这里值得注意,因为它也可能在以后引起问题。

不管怎样,Zoran Regvart 展示的方法基本上是可行的。给定形式的代码可能存在一些舍入问题。但是,这种情况的总体思路总是相同的:

您有一个源区间 [minA...maxA] 您有一个目标区间 [minB...maxB] 您需要两者之间的映射

在这种情况下,标准化间隔是一个很好的做法。即把源区间的值映射到0.0到1.0之间的一个值,再把这个归一化的值映射到目标区间。

在最通用的形式中,这可以写成

long minA = ...
long maxA = ...
long a = ... // The current value in the source interval
int minB = ...
int maxB = ...
int b; // The value to compute in the target interval

// Map the first value to a value between 0.0 and 1.0
double normalized = (double)(a - minA)/(maxA-minA);
b = (int)(minB + normalized * (maxB - minB));

幸运的是,您的“最小”值在这里都为零,所以它更简单一些。这是一个MCVE(带有一些虚拟类)。最相关的部分是底部的updateSlider 方法。

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;

public class SliderMappingTest

    public static void main(String[] args)
    
        SwingUtilities.invokeLater(new Runnable()
        
            @Override
            public void run()
            
                createAndShowGUI();
            
        );
    

    private static void createAndShowGUI()
    
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


        final JButton startButton = new JButton("Start");
        final JSlider progressSlider = new JSlider(0, 1000, 0);

        startButton.addActionListener(new ActionListener()
        
            @Override
            public void actionPerformed(ActionEvent e)
            
                startButton.setEnabled(false);
                SliderMappingDummyAudioClip audioClip = 
                    new SliderMappingDummyAudioClip();
                SliderMappingDummyPlayer player = 
                    new SliderMappingDummyPlayer(progressSlider, audioClip);
                player.start();
            
        );


        f.getContentPane().setLayout(new GridLayout());
        f.getContentPane().add(startButton);
        f.getContentPane().add(progressSlider);

        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    


class SliderMappingDummyAudioClip

    private long startMicros;

    void start()
    
        startMicros = System.nanoTime() / 1000L;
    

    long getMicrosecondLength()
    
        // 10 seconds
        return 10L * 1000L * 1000L;
    

    long getMicrosecondPosition()
    
        return (System.nanoTime() / 1000L) - startMicros;
    

    public boolean isRunning()
    
        return getMicrosecondPosition() <= getMicrosecondLength();
    



class SliderMappingDummyPlayer

    private final SliderMappingDummyAudioClip audioClip;
    private final JSlider slider;

    SliderMappingDummyPlayer(
        JSlider slider, 
        SliderMappingDummyAudioClip audioClip)
    
        this.slider = slider;
        this.audioClip = audioClip;
    

    void start()
    
        Thread t = new Thread(new Runnable()
        
            @Override
            public void run()
            
                doRun();
            
        );
        t.setDaemon(true);
        t.start();
    

    private void doRun()
    
        audioClip.start();
        while (audioClip.isRunning()) 
        
            updateSlider();
            try 
            
                Thread.sleep(30);
            
            catch (InterruptedException ex) 
            
                Thread.currentThread().interrupt();
                return;
            
                
    

    private void updateSlider()
    
        long currentMicros = audioClip.getMicrosecondPosition();

        // Compute the progress as a value between 0.0 and 1.0
        double progress = 
            (double)currentMicros / audioClip.getMicrosecondLength();

        // Compute the slider value to indicate the progress
        final int sliderValue = (int)(progress * slider.getMaximum());

        System.out.println("update "+progress);

        // Update the slider with the new value, on the Event Dispatch Thread
        SwingUtilities.invokeLater(new Runnable()
        
            @Override
            public void run()
            
                slider.setValue(sliderValue);
            
        );
    


【讨论】:

这太棒了,非常感谢。但是,它还没有解决我的问题。我稍后会发布一个 MVCE【参考方案4】:

您确定要将最大值设置为当前位置吗?

如何通过除法将 long 映射到 int:

long coefficient = clip.getMicrosecondLength() / Integer.MAX_VALUE;

slider.setMinimum(0);
slider.setMaximum((int) (clip.getMicrosecondLength() / coefficient));
...
slider.setValue((int) (clip.getMicrosecondPosition() / coefficient));

【讨论】:

我在 setMaximum 和 setValue 上收到此错误:java.lang.ArithmeticException: / by zero @QuentinTanioartino 这个答案只展示了基本思想。通常,您通过 规范化 值来定义源区间和目标区间之间的映射 - 即将值映射到 0.01.0 的范围,并使用以下命令执行所有计算double 值(仅在最后时刻转换为 int,在滑块中设置值时)

以上是关于JSlider 问题音频播放器的主要内容,如果未能解决你的问题,请参考以下文章

多个 HTML5 音频播放器。单击另一个音频实例时停止播放其他音频实例

如果选择了一个播放器,如何停止所有音频播放器的播放?

当另一个应用开始播放音频时,如何自动暂停我的应用中的音频播放器?

是否有可嵌入 HTML 的音频播放器阻止下载它播放的音频?

iOS音频播放

Html音频播放器音频停在中间