Звуковой проигрыватель JSlider

Я пытаюсь создать аудиоплеер со встроенным 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;
 }
 }
 }
}

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
 ****** progress = 
 (******)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);

 }
}
4 ответа

Итак, проблема с 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 (com.sun.media.sound.MixerClip) возвращает 0 для позиции, а клип на моем компьютере под управлением Windows (com.sun.media.sound.DirectAudioDevice$DirectClip) возвращает максимальное значение для позиции. Еще один небольшой пример реализации программ по-разному.

Проблема заключается в том, что контракт для этих методов определен немного неопределенно, но, в частности, он определяется "количеством выборочных кадров, захваченных или отображенных с линии после ее открытия". Это означает, что он не может точно представлять позицию воспроизведения, а это количество прочитанных и записанных данных.

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

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

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

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

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

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

Этот пример не демонстрирует поиск. Кроме того, поскольку 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(****** elapsed, ****** total) {
 final ****** 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");
 }
 });
 }
 }
}


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

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

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

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


В коде есть несколько проблем.

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

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

  • У вас есть интервал источника [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
****** normalized = (******)(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
 ****** progress = 
 (******)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);
 }
 });
 }

}


Вы действительно хотите установить максимальную текущую позицию?

Как насчет привязки longs к ints путем деления:

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

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

licensed under cc by-sa 3.0 with attribution.