Search code examples
javaswingjslider

How to set a JSlider to the duration of an audio file?


Is this event possible to implement in a JSlider or not? Also, can one be able to move the slider to a certain point in the audio file? To Elaborate, the user can drag the slider to any point in the JSlider which causes the playback to "rewind" or "skip".


Solution

  • In situation like this, you google. No I'm serious.

    I used the answer from java wav player adding pause and continue. and How do I get a sound file's total time in Java? and combined them into a solution which allows a JSlider to display the current playback position AND change the current playback position.

    The important thing to remember when trying to solve problems like this is - it's unlikely you will find an exact matching solution and you're going to have to adapt a number of ideas to meet your needs.

    While the following "might" look like a fully fledge system, it's just a demonstration - it needs a lot of additional work and management to make it flexible and robust - this is something I'll leave you to figure out.

    The core functionality is as follows...

    AudioInputStream ais = null;
    try {
        File file = new File(...);
        ais = AudioSystem.getAudioInputStream(file);
        format = ais.getFormat();
        frameCount = ais.getFrameLength();
        duration = ((double) frameCount) / format.getFrameRate();
    
        clip = AudioSystem.getClip();
        clip.open(ais);
    } catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
        ex.printStackTrace();
    }
    

    This basically loads a audio file, obtains the frameCount and uses it to calculate the duration in seconds. It then creates a Clip which can be used play the audio file.

    From there, the reset is just about monitoring the framePosition while the audio is been played (I use a Swing Timer) and updating the state.

    When the JSlider is changed by the user, this simply sets the framePosition to the desired frame

    For example...

    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.File;
    import java.io.IOException;
    import javax.sound.sampled.AudioFormat;
    import javax.sound.sampled.AudioInputStream;
    import javax.sound.sampled.AudioSystem;
    import javax.sound.sampled.Clip;
    import javax.sound.sampled.LineEvent;
    import javax.sound.sampled.LineEvent.Type;
    import javax.sound.sampled.LineListener;
    import javax.sound.sampled.LineUnavailableException;
    import javax.sound.sampled.UnsupportedAudioFileException;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JSlider;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.event.ChangeEvent;
    import javax.swing.event.ChangeListener;
    
    public class Test {
    
        public static void main(String[] args) {
            new Test();
        }
    
        public Test() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private JSlider slider = new JSlider(0, 100);
            private long frameCount;
            private double duration;
    
            private AudioFormat format;
            private Clip clip;
    
            private JLabel currentFrame;
            private JLabel currentDuration;
    
            private boolean playing = false;
    
            private Timer playTimer;
    
            private boolean ignoreStateChange = false;
    
            public TestPane() {
                AudioInputStream ais = null;
                try {
                    File file = new File(System.getProperty("user.home") + "/Library/Application Support/Steam/Steam.AppBundle/Steam/Contents/MacOS/friends/voice_hang_up.wav");
                    ais = AudioSystem.getAudioInputStream(file);
                    format = ais.getFormat();
                    frameCount = ais.getFrameLength();
                    duration = ((double) frameCount) / format.getFrameRate();
    
                    clip = AudioSystem.getClip();
                    clip.open(ais);
                } catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
                    ex.printStackTrace();
                }
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridwidth = GridBagConstraints.REMAINDER;
                setLayout(new GridBagLayout());
                add(slider, gbc);
                slider.setValue(0);
    
                add(new JLabel("Total Frames: " + frameCount), gbc);
                add(new JLabel("Total Duration: " + duration), gbc);
    
                currentFrame = new JLabel("Current frame: 0");
                currentDuration = new JLabel("Current duration: 0");
    
                add(currentFrame, gbc);
                add(currentDuration, gbc);
    
                JButton action = new JButton("Play");
                action.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        if (!playing) {
                            int frame = getDesiredFrame();
                            if (frame >= frameCount) {
                                frame = 0;
                            }
                            clip.setFramePosition(frame);
                            clip.start();
                            action.setText("Stop");
                            playing = true;
                            playTimer.start();
                        } else {
                            clip.stop();
                            action.setText("Play");
                            playing = false;
                            playTimer.stop();
                        }
                    }
                });
    
                clip.addLineListener(new LineListener() {
                    @Override
                    public void update(LineEvent event) {
                        if (event.getType().equals(Type.STOP)
                                        || event.getType().equals(Type.CLOSE)) {
                            action.setText("Play");                     
                            playing = false;
                            playTimer.stop();
                            updateState();
                        }
                    }
                });
    
                add(action, gbc);
    
                playTimer = new Timer(100, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        updateState();
                    }
                });
    
                Timer delayedUpdate = new Timer(250, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int frame = getDesiredFrame();
                        clip.setFramePosition(frame);
    
                        double time = getCurrentTime();
    
                        currentFrame.setText("Current frame: " + frame);
                        currentDuration.setText("Current duration: " + time);
    
                    }
                });
                delayedUpdate.setRepeats(false);
                slider.addChangeListener(new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        if (ignoreStateChange) {
                            return;
                        }
                        delayedUpdate.restart();
                    }
                });
            }
    
            public void updateState() {
                ignoreStateChange = true;
                int frame = clip.getFramePosition();
                int progress = (int) (((double) frame / (double) frameCount) * 100);
                slider.setValue(progress);
                currentFrame.setText("Current frame: " + getDesiredFrame());
                currentDuration.setText("Current duration: " + getCurrentTime());
                ignoreStateChange = false;
            }
    
            public double getCurrentTime() {
                int currentFrame = clip.getFramePosition();
                double time = (double) currentFrame / format.getFrameRate();
                return time;
            }
    
            public int getDesiredFrame() {
                int progress = slider.getValue();
                double frame = ((double) frameCount * ((double) progress / 100.0));
                return (int) frame;
            }
    
        }
    
    }