我创建了一个线程,通过将其转换为字节数组来播放Java中的mp3文件。
我想知道我是否可以在播放mp3时跟踪当前的播放位置。
首先,我设置了我的音乐流:
try {
AudioInputStream in = AudioSystem.getAudioInputStream(file);
musicInputStream = AudioSystem.getAudioInputStream(MUSIC_FORMAT, in);
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, MUSIC_FORMAT);
musicDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
musicDataLine.open(MUSIC_FORMAT);
musicDataLine.start();
startMusicThread();
} catch(Exception e) {
e.printStackTrace();
}
接下来,我的音乐主题看起来像这样:
private class MusicThread extends Thread {
byte musicBuffer[] = new byte[BUFFER_SIZE];
public void run() {
try {
int musicCount = 0;
while(writeOutput){
if(writeMusic && (musicCount = musicInputStream.read(musicBuffer, 0, musicBuffer.length)) > 0){
musicDataLine.write(musicBuffer, 0, musicCount);
}
}
} catch (Exception e) {
System.out.println("AudioStream Exception - Music Thread"+e);
e.printStackTrace();
}
}
}
我想到了一种可能性,用一个慢慢滴答的计时器创建另一个线程,逐秒,以显示mp3歌曲的剩余时间。但这似乎并不是一个好的解决方案。
答案 0 :(得分:2)
你的int musicCount
(来自AudioInputStream.read(...)
的返回值)告诉你读取的字节数,所以你可以做一个小的计算来总是找出你在流中的位置。 (DataLine
有一些方法可以为你做一些数学计算,但不能总是使用它们......见下文。)
int musicCount = 0;
int totalBytes = 0;
while ( loop stuff ) {
// accumulate it
// and do whatever you need with it
totalBytes += musicCount;
musicDataLine.write(...);
}
要获得经过的秒数,您可以执行以下操作:
AudioFormat fmt = musicInputStream.getFormat();
long framesRead = totalBytes / fmt.getFrameSize();
long totalFrames = musicInputStream.getFrameLength();
double totalSeconds = (double) totalFrames / fmt.getSampleRate();
double elapsedSeconds =
((double) framesRead / (double) totalFrames) * totalSeconds;
因此,您只需获取每个循环所用的时间,并将其放在需要它的地方。请注意,这种精确度取决于缓冲区的大小。缓冲区越小,越准确。
此外,Clip
有一些方法可以为您查询(但您可能需要更改您正在做的事情)。
这些方法(get(Long)FramePosition
/ getMicrosecondPosition
)继承自DataLine
,因此如果您不想这样做,也可以在SourceDataLine
上调用它们你自己的数学。 然而,您基本上需要为您播放的每个文件创建一个新行,因此这取决于您使用该行的方式。 (就个人而言,我更愿意自己做分工,因为要求线条是不透明的。)
顺便说一句:
musicDataLine.open(MUSIC_FORMAT);
您应该使用(AudioFormat, int)
重载打开指定了您自己的缓冲区大小的行。 SourceDataLine.write(...)
仅在其内部缓冲区已满时阻塞,因此如果它与字节数组的大小不同,有时您的循环会阻塞,有时它只是旋转。
MCVE的好措施:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.sound.sampled.*;
public class SimplePlaybackProgress
extends WindowAdapter implements Runnable, ActionListener {
class AudioPlayer extends Thread {
volatile boolean shouldPlay = true;
final int bufferSize;
final AudioFormat fmt;
final AudioInputStream audioIn;
final SourceDataLine audioOut;
final long frameSize;
final long totalFrames;
final double sampleRate;
AudioPlayer(File file)
throws UnsupportedAudioFileException,
IOException,
LineUnavailableException {
audioIn = AudioSystem.getAudioInputStream(file);
fmt = audioIn.getFormat();
bufferSize = fmt.getFrameSize() * 8192;
frameSize = fmt.getFrameSize();
totalFrames = audioIn.getFrameLength();
sampleRate = fmt.getSampleRate();
try {
audioOut = AudioSystem.getSourceDataLine(audioIn.getFormat());
audioOut.open(fmt, bufferSize);
} catch (LineUnavailableException x) {
try {
audioIn.close();
} catch(IOException suppressed) {
// Java 7+
// x.addSuppressed(suppressed);
}
throw x;
}
}
@Override
public void run() {
final byte[] buffer = new byte[bufferSize];
long framePosition = 0;
try {
audioOut.start();
while (shouldPlay) {
int bytesRead = audioIn.read(buffer);
if (bytesRead < 0) {
break;
}
int bytesWritten = audioOut.write(buffer, 0, bytesRead);
if (bytesWritten != bytesRead) {
// shouldn't happen
throw new RuntimeException(String.format(
"read: %d, wrote: %d", bytesWritten, bytesRead));
}
framePosition += bytesRead / frameSize;
// or
// framePosition = audioOut.getLongFramePosition();
updateProgressBar(framePosition);
}
audioOut.drain();
audioOut.stop();
} catch (Throwable x) {
showErrorMessage(x);
} finally {
updateProgressBar(0);
try {
audioIn.close();
} catch (IOException x) {
showErrorMessage(x);
}
audioOut.close();
}
}
void updateProgressBar(
final long framePosition) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
double fractionalProgress =
(double) framePosition / (double) totalFrames;
int progressValue = (int) Math.round(
fractionalProgress * theProgressBar.getMaximum());
theProgressBar.setValue(progressValue);
int secondsElapsed = (int) Math.round(
(double) framePosition / sampleRate);
int minutes = secondsElapsed / 60;
int seconds = secondsElapsed % 60;
theProgressBar.setString(String.format(
"%d:%02d", minutes, seconds));
}
});
}
void stopPlaybackAndDrain() throws InterruptedException {
shouldPlay = false;
this.join();
}
}
/* * */
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimplePlaybackProgress());
}
JFrame theFrame;
JButton theButton;
JProgressBar theProgressBar;
// this should only ever have 1 thing in it...
// multithreaded code with poor behavior just bugs me,
// even for improbable cases, so the queue makes it more robust
final Queue<AudioPlayer> thePlayerQueue = new ArrayDeque<AudioPlayer>();
@Override
public void run() {
theFrame = new JFrame("Playback Progress");
theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
theButton = new JButton("Open");
theProgressBar = new JProgressBar(
SwingConstants.HORIZONTAL, 0, 1000);
theProgressBar.setStringPainted(true);
theProgressBar.setString("0:00");
Container contentPane = theFrame.getContentPane();
((JPanel) contentPane).setBorder(
BorderFactory.createEmptyBorder(8, 8, 8, 8));
contentPane.add(theButton, BorderLayout.WEST);
contentPane.add(theProgressBar, BorderLayout.CENTER);
theFrame.pack();
theFrame.setResizable(false);
theFrame.setLocationRelativeTo(null);
theFrame.setVisible(true);
theButton.addActionListener(this);
theFrame.addWindowListener(this);
}
@Override
public void actionPerformed(ActionEvent ae) {
JFileChooser dialog = new JFileChooser();
int option = dialog.showOpenDialog(theFrame);
if (option == JFileChooser.APPROVE_OPTION) {
File file = dialog.getSelectedFile();
try {
enqueueNewPlayer(new AudioPlayer(file));
} catch (UnsupportedAudioFileException x) { // ew, Java 6
showErrorMessage(x); //
} catch (IOException x) { //
showErrorMessage(x); //
} catch (LineUnavailableException x) { //
showErrorMessage(x); //
} //
}
}
@Override
public void windowClosing(WindowEvent we) {
stopEverything();
}
void enqueueNewPlayer(final AudioPlayer newPlayer) {
// stopPlaybackAndDrain calls join
// so we want to do it off the EDT
new Thread() {
@Override
public void run() {
synchronized (thePlayerQueue) {
stopEverything();
newPlayer.start();
thePlayerQueue.add(newPlayer);
}
}
}.start();
}
void stopEverything() {
synchronized (thePlayerQueue) {
while (!thePlayerQueue.isEmpty()) {
try {
thePlayerQueue.remove().stopPlaybackAndDrain();
} catch (InterruptedException x) {
// shouldn't happen
showErrorMessage(x);
}
}
}
}
void showErrorMessage(Throwable x) {
x.printStackTrace(System.out);
String errorMsg = String.format(
"%s:%n\"%s\"", x.getClass().getSimpleName(), x.getMessage());
JOptionPane.showMessageDialog(theFrame, errorMsg);
}
}
对于Clip
,你只需要一个类似Swing计时器(或其他副线程)的东西,然后经常查询它:
new javax.swing.Timer(100, new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
long usPosition = theClip.getMicrosecondPosition();
// put it somewhere
}
}).start();
相关: