2014-11-13 2 views
0

Я пытаюсь создать аудиоплеер со встроенным JSlider, который обновляет интерфейс каждые микросекунды.JSlider issue audio player

Для того, чтобы сделать это, я использую следующее:

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

AudioPlayer

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

PlayingTimer

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

} 

PlayerView

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

     } 
} 
+0

На самом деле, 'slider.setValue' вызов должен быть сделан на тему событий диспетчерской (обернуть его в' SwingUtilities.invokeLater' вызова). Однако это не должно вызывать ЭТОЙ проблемы. Http://stackoverflow.com/help/mcve было бы неплохо. Но одна общая стратегия в таких случаях аналогична тому, что предложил Зоран: Определите ползунок, чтобы иметь «разумный» интервал, и пропорционально сопоставляйте фактический интервал с интервалом ползунка. – Marco13

+0

Спасибо, Jslider всегда перекрашивается с помощью вызова SwingUtilities.invokeLater :) – QGA

+0

Чтобы быть ясным: вызов 'setValue' изменяет модель, то есть состояние компонента Swing. Таким образом, вызов 'setValue' должен быть ** также ** выполнен в Thread Dispatch Thread. – Marco13

ответ

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, но вы можете запустить этот код чтобы узнать, работает ли это на вашей конкретной платформе.

Итак, проблема заключается в том, что, как я сказал в комментариях, поскольку воспроизведение звука зависит от специфики платформы, такие классы, как Clip, будут иметь различные реализации. Они будут вести себя по-другому.

Например, я обнаружил, что при выполнении клипа на клипе Mac на компьютере Mac (com.sun.media.sound.MixerClip) возвращается 0 для позиции, а клип на моем компьютере под управлением Windows (com.sun.media.sound.DirectAudioDevice$DirectClip) возвращает максимальное значение для должность. Еще один небольшой пример реализации программ по-разному.

Вопрос заключается в том, что договор для these methods определен немного неопределенно, но, в частности, он определяется «количеством отснятых кадров, снятых или отведенных от линии с момента ее открытия». Это означает, что он может неточно представлять позицию , а это количество данных, считанных и записанных.

Вчера я потратил некоторое время на изучение исходного кода JDK, но я не смог найти ничего, что указывало бы на поведение, которое вы видите.

Как бы то ни было, все зависит от того, согласны ли вы с незначительными аномальными поведенческими различиями от платформы к платформе. Что вы видите может быть быть ошибкой, и если вышеуказанный MCVE воспроизводит его, вы можете сообщить об этом; однако я лично не ожидал, что он будет исправлен своевременно, потому что это раздел JDK, который не получает большого внимания. Также он постепенно заменяется JavaFX.

Некоторые другие вещи:

  • Вы разделяете состояние между потоками без синхронизации. Это приводит к ошибкам памяти. Вы должны прочитать concurrency tutorials, в частности synchronization.
  • При работе с Swing всегда должна быть установлена ​​частота кадров. Swing не будет рисовать при 1000FPS, он будет агрессивно сжиматься. Обновление слайдера с такой скоростью только наводнение EDT.

Вы можете использовать SourceDataLine, потому что это дает вам больший контроль над поведением буферизации. Недостатком является то, что вы должны в основном переопределить функциональность Clip.

Вот MCVE, демонстрирующий цикл воспроизведения для питания JSlider.

PlaybackSlider

Этот пример не демонстрирует поиск. Кроме того, поскольку AudioInputStream обычно не поддерживает операции с метками, поиск назад - это немного хлопот. Процесс обратного хода:

  • Остановите текущее воспроизведение и отбросьте его.
  • Создайте новый AudioInputStream и ищите вперед.
  • Запустите новое воспроизведение.

Кроме того, если вы планируете использовать JSlider искать, вы, вероятно, столкнуться в к проблеме, где вызов setValue на JSlider заставит его уволить ChangeEvent. Таким образом, вы не можете программно обновлять значение слайдера, а также прослушивать его, не перегружая его. Это действительно Q & A, поэтому, если у вас возникла эта проблема, я рекомендую вам задать новый вопрос.

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"); 
       } 
      }); 
     } 
    } 
} 
0

Вы уверены, что хотите установить максимум к текущей позиции?

Как насчет отображения длинных позиций в Интс по дивизиону:

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

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

Я получаю эту ошибку на setMaximum и setValue: java.lang.ArithmeticException:/нолем – QGA

+1

@QuentinTanioartino Этот ответ только Основная идея. Обычно вы определяете сопоставление между источником и целевым интервалом посредством * нормализации * значения, то есть путем сопоставления значения с диапазоном «0.0» и «1.0» и выполнения всех вычислений с использованием 'double' (только в режиме« int »в последний момент при установке значения в слайдере) – Marco13

1

Я предполагаю, что вы желаете, чтобы использовать JSlider как индикатор выполнения и что в данный момент вы устанавливаете максимальное значение, текущее положение в конец аудиоклипа. (Вы имеете дело с Clip или AudioClip? AudioClip не имеет возможности прочитать свою позицию AFAIK.) Если вы используете Clip, было бы безопаснее установить max с помощью audioClip.getMicrosecondLength().

Поскольку звук должен воспроизводиться в другом потоке, чем тот, где обновляется JSlider, я бы рекомендовал сделать ваш audioClip изменчивой переменной. Это может помочь с перекрестной нитью, которая иногда возникает.

Thread.sleep (1) в лучшем случае может обновлять только каждые миллисекунды. В некоторых системах (более старые Windows) зависимость метода от системных часов означает, что фактические обновления так же медленны, как и 16 миллисекунд. Но обновление JSlider более чем 60 кадров в секунду, вероятно, спорный вопрос. Экранные мониторы часто устанавливаются на 60 Гц, и есть только так много человеческого глаза, которое может занять.

Также есть только так много уха, которое можно различить с точки зрения времени. Например, трудно сказать, происходят ли два перкуссионных события одновременно, если разница в пару миллисекунд меньше.

+0

Спасибо, этот ответ получил множество советов – QGA

+1

Я хотел добавить еще одну морщину: JVM не обрабатывает звук в реальном времени. Вместо этого он делает это в «срезах», когда он переключается между потоками. Таким образом, он немного опередит фактическое воспроизведение, переключится на другие вещи, такие как ваш слайдер, а затем вернется и сделает еще несколько аудио. Это может сделать ваш прогресс немного напряженным, поскольку он не обновляется при обработке аудио и наоборот. Эффект становится более очевидным и более раздражающим, когда вы пытаетесь, скажем, прочитать положение мыши, как Термен и получить плавные обновления тона. Но для индикатора прогресса это не должно быть столь же точным. –

1

С кодом возникает несколько проблем.

Как заметил Фил Фрейхофнер, sleep(1) и обработка полей isRunning и isPause выглядят весьма сомнительными. В некоторой степени это не связано с вашим фактическим вопросом, но стоит отметить здесь, потому что это может также вызвать проблемы позже.

Независимо от этого, подход, который показал Зоран Регварт, в основном - путь. Код в данной форме, возможно, пострадал от некоторых вопросов округления. Тем не менее, общая идея для случаев, как это всегда то же самое:

  • У вас есть интервал источника [Mina ... MAXA]
  • У вас есть целевой интервал [МИнБ ... 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); 
      } 
     }); 
    } 

} 
+0

Это потрясающе, спасибо большое. Тем не менее, он еще не решил мою проблему. Я отправлю сообщение MVCE позже – QGA