为什么在音频片段来回拖动时,它们就会失效?
我想做一个音频播放器,仅仅是为了更好地理解Java中的栈和GUI。原本我预计选中音频文件后它会播放,之前是会的,但现在并没有播放。我又回到过去的代码里查看差异,但没有发现任何应该导致它无法工作的差异,在终端也没有报错。
我也尝试通读Java SE 8的文档,以及一些关于Java中音频剪辑的视频,似乎都和我所知的一样正确,不过不确定问题出在哪里;另外,为了测试,我从代码中移除了 current = numberofClips;,结果只有两段音频中的其中一个能播放,原因不明。
以下是代码:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.io.File;
import java.util.Stack;
import javax.imageio.ImageIO;
import javax.sound.sampled.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
public class MusicPlayerGui extends JFrame implements ActionListener {
// Image centered
BufferedImage image = null;
// Colors
public static final Color BG_Color = Color.BLACK;
public static final Color FG_Color = Color.WHITE;
// Song
JLabel songImage;
JLabel songTitle;
JSlider playbackSlider;
// Toolbar
JToolBar toolbar;
JMenuBar menuBar;
JMenu songMenu;
JMenuItem loadMusic;
JMenuItem loadPlaylist;
// Button
JPanel playbackBtns;
JButton prevButton;
JButton playButton;
JButton pauseButton;
JButton nextButton;
// Music stuff
Stack<Clip> clips = new Stack<>();
int next;
int prev;
long frames;
int current = 0;
float frameRate;
int numberofClips;
AudioFormat format;
boolean play = true;
boolean first = true;
double durationInSeconds;
long seconds;
boolean playlist;
public MusicPlayerGui() {
super("Music Player");
setSize(400, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
setLayout(null);
getContentPane().setBackground(BG_Color);
addUiComponents();
}
private void addUiComponents() {
addToolbar();
addPlaybackBtns();
songImage = new JLabel(loadImage("./res/record.png"));
songImage.setBounds(0, 50, getWidth() - 20, 225);
add(songImage);
songTitle = new JLabel("Song title");
songTitle.setBounds(0, 285, getWidth() - 10, 30);
songTitle.setFont(new Font("Dialog", Font.BOLD, 24));
songTitle.setForeground(FG_Color);
songTitle.setHorizontalAlignment(SwingConstants.CENTER);
add(songTitle);
playbackSlider = new JSlider();
playbackSlider.setBounds(getWidth() / 2 - 300 / 2, 365, 300, 40);
playbackSlider.setBackground(null);
add(playbackSlider);
}
private void addToolbar() {
toolbar = new JToolBar();
toolbar.setBounds(0, 0, getWidth(), 30);
toolbar.setFloatable(false);
add(toolbar);
menuBar = new JMenuBar();
toolbar.add(menuBar);
songMenu = new JMenu("Song");
menuBar.add(songMenu);
loadMusic = new JMenuItem("Load music from directory");
loadMusic.addActionListener(this);
songMenu.add(loadMusic);
loadPlaylist = new JMenuItem("Load playlist from directory");
loadPlaylist.addActionListener(this);
songMenu.add(loadPlaylist);
}
private void addPlaybackBtns() {
playbackBtns = new JPanel();
playbackBtns.setBounds(0, 435, getWidth() - 10, 80);
playbackBtns.setBackground(null);
prevButton = new JButton(loadImage("./res/previous.png"));
prevButton.setBorderPainted(false);
prevButton.setBackground(null);
prevButton.addActionListener(this);
playbackBtns.add(prevButton);
playButton = new JButton(loadImage("./res/play.png"));
playButton.setBorderPainted(false);
playButton.setBackground(null);
playButton.addActionListener(this);
playbackBtns.add(playButton);
pauseButton = new JButton(loadImage("./res/pause.png"));
pauseButton.setBorderPainted(false);
pauseButton.setBackground(null);
pauseButton.setVisible(false);
pauseButton.addActionListener(this);
playbackBtns.add(pauseButton);
nextButton = new JButton(loadImage("./res/next.png"));
nextButton.setBorderPainted(false);
nextButton.setBackground(null);
playbackBtns.add(nextButton);
add(playbackBtns);
}
private ImageIcon loadImage(String imagePath) {
try {
BufferedImage image = ImageIO.read(new File(imagePath));
return new ImageIcon(image);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == loadMusic) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("Music Files", "wav"));
int response = fileChooser.showOpenDialog(null);
if (response == JFileChooser.APPROVE_OPTION) {
File file = new File(fileChooser.getSelectedFile().getAbsolutePath());
try (AudioInputStream aS = AudioSystem.getAudioInputStream(file)) {
if (play == true) {
enablePlayButton();
clips.get(current).stop();
}
numberofClips++;
Clip clip = AudioSystem.getClip();
current = numberofClips;
format = aS.getFormat();
frames = aS.getFrameLength();
durationInSeconds = frames / format.getFrameRate();
seconds = (long) (durationInSeconds % 60);
clip.open(aS);
clips.push(clip);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
System.out.println(ex);
}
}
}
if (e.getSource() == loadPlaylist) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int response = fileChooser.showOpenDialog(null);
if (response == JFileChooser.APPROVE_OPTION) {
File files = new File(fileChooser.getSelectedFile().getAbsolutePath());
for (File file : files.listFiles()) {
try (AudioInputStream aS = AudioSystem.getAudioInputStream(file)) {
if (play == true) {
enablePlayButton();
clips.get(current).stop();
}
numberofClips++;
Clip clip = AudioSystem.getClip();
format = aS.getFormat();
frames = aS.getFrameLength();
durationInSeconds = frames / format.getFrameRate();
seconds = (long) (durationInSeconds % 60);
playlist = true;
clip.open(aS);
clips.push(clip);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
System.out.println(ex);
}
}
}
}
if (e.getSource() == playButton) {
enablePauseButton();
clips.get(current).start();
next = current + 1;
prev = current - 1;
if (playlist) {
if ((clips.empty() && clips.get(current).isRunning() != false) && next > numberofClips) {
enablePauseButton();
clips.get(next).start();
current = next;
}
}
}
if (e.getSource() == pauseButton) {
enablePlayButton();
clips.get(current).stop();
}
if (e.getSource() == prevButton) {
if (first != true && prev < 0) {
enablePlayButton();
clips.get(current).stop();
clips.get(prev).start();
current = prev;
next = current + 1;
prev = current - 1;
}
}
}
void enablePauseButton() {
JButton playBtn = (JButton) playbackBtns.getComponent(1);
JButton pauseBtn = (JButton) playbackBtns.getComponent(2);
playBtn.setVisible(false);
playBtn.setEnabled(false);
pauseBtn.setVisible(true);
pauseBtn.setEnabled(true);
play = true;
}
void enablePlayButton() {
JButton playBtn = (JButton) playbackBtns.getComponent(1);
JButton pauseBtn = (JButton) playbackBtns.getComponent(2);
playBtn.setVisible(true);
playBtn.setEnabled(true);
pauseBtn.setVisible(false);
pauseBtn.setEnabled(false);
play = false;
}
}
另外,这里是主文件,便于运行:
import javax.swing.SwingUtilities;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new MusicPlayerGui().setVisible(true);;
}
});
}
}
解决方案
在实现了几个缺失的方法(addPlaybackBtns、enablePlayButton 和 enablePauseButton)之后,我觉得这个最小可复现示例大致实现如下代码:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Stack;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
public class AssumedMRE {
public static class MusicPlayerGui extends JFrame implements ActionListener {
JMenu songMenu;
JMenuItem loadMusic;
JMenuItem loadPlaylist;
// Button
JPanel playbackBtns;
JButton prevButton;
JButton playButton;
JButton pauseButton;
JButton nextButton;
// Music stuff
Stack<Clip> clips = new Stack<>();
int next;
int prev;
int current = 0;
float frameRate;
int numberofClips;
boolean play = true;
boolean first = true;
boolean playlist;
public MusicPlayerGui() {
super("Music Player");
setSize(400, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
//setLayout(null);
setLayout(new GridLayout(0, 1));
//getContentPane().setBackground(BG_Color);
// Ignore this
addUiComponents();
}
private void addUiComponents() {
addToolbar();
addPlaybackBtns();
// removed the rest of the function
}
//Modified method.
private void addToolbar() {
//toolbar = new JToolBar();
//toolbar.setBounds(0, 0, getWidth(), 30);
//toolbar.setFloatable(false);
//add(toolbar);
//menuBar = new JMenuBar();
//toolbar.add(menuBar);
final JMenuBar menuBar = new JMenuBar();
super.setJMenuBar(menuBar);
songMenu = new JMenu("Song");
menuBar.add(songMenu);
loadMusic = new JMenuItem("Load music from directory");
loadMusic.addActionListener(this);
songMenu.add(loadMusic);
loadPlaylist = new JMenuItem("Load playlist from directory");
loadPlaylist.addActionListener(this);
songMenu.add(loadPlaylist);
}
//Assumed method body...
private void addPlaybackBtns() {
playbackBtns = new JPanel();
super.add(playbackBtns);
prevButton = new JButton("Previous");
prevButton.addActionListener(this);
playbackBtns.add(prevButton);
playButton = new JButton("Play");
playButton.addActionListener(this);
playbackBtns.add(playButton);
pauseButton = new JButton("Pause");
pauseButton.addActionListener(this);
playbackBtns.add(pauseButton);
nextButton = new JButton("Next");
nextButton.addActionListener(this);
playbackBtns.add(nextButton);
}
//Assumed method body...
private void enablePlayButton() {
playButton.setEnabled(true);
pauseButton.setEnabled(false);
}
//Assumed method body...
private void enablePauseButton() {
playButton.setEnabled(false);
pauseButton.setEnabled(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == loadMusic) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("Music Files", "wav"));
int response = fileChooser.showOpenDialog(null);
if (response == JFileChooser.APPROVE_OPTION) {
File file = new File(fileChooser.getSelectedFile().getAbsolutePath());
try (AudioInputStream aS = AudioSystem.getAudioInputStream(file)) {
if (play == true) {
enablePlayButton();
clips.get(current).stop();
}
numberofClips++;
Clip clip = AudioSystem.getClip();
current = numberofClips;
clip.open(aS);
clips.push(clip);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
System.out.println(ex);
}
}
}
if (e.getSource() == loadPlaylist) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int response = fileChooser.showOpenDialog(null);
if (response == JFileChooser.APPROVE_OPTION) {
File files = new File(fileChooser.getSelectedFile().getAbsolutePath());
for (File file : files.listFiles()) {
try (AudioInputStream aS = AudioSystem.getAudioInputStream(file)) {
if (play == true) {
enablePlayButton();
clips.get(current).stop();
}
numberofClips++;
Clip clip = AudioSystem.getClip();
playlist = true;
clip.open(aS);
clips.push(clip);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
System.out.println(ex);
}
}
}
}
if (e.getSource() == playButton) {
enablePauseButton();
clips.get(current).start();
next = current + 1;
prev = current - 1;
if (playlist) {
if ((clips.empty() && clips.get(current).isRunning() != false) && next > numberofClips) {
enablePauseButton();
clips.get(next).start();
current = next;
}
}
}
if (e.getSource() == pauseButton) {
enablePlayButton();
clips.get(current).stop();
}
if (e.getSource() == prevButton) {
if (first != true && prev < 0) {
enablePlayButton();
clips.get(current).stop();
clips.get(prev).start();
current = prev;
next = current + 1;
prev = current - 1;
}
}
//Assumed extra code:
if (e.getSource() == nextButton) {
if (!clips.isEmpty() && next < clips.size()) {
enablePlayButton();
clips.get(current).stop();
clips.get(next).start();
current = next;
next = current + 1;
prev = current - 1;
}
}
}
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> new MusicPlayerGui().setVisible(true));
}
}
需要修复的一些问题包括:
- 变量
play在初始时应以false作为起点,因为没有正在播放的音轨。如果使用true,在加载音乐文件时应用程序会崩溃,因为它试图从剪辑栈中停止当前正在播放的剪辑(但clips栈是空的,因此会得到一个ArrayIndexOutOfBoundsException)。而且该变量的值在代码中没有在任何地方改变,这就使它的作用失效。最后如果该变量用来指示当前是否正在播放歌曲,那么这个变量是冗余的(不需要),因为我们可以在当前剪辑上使用isActive()来进行判断。 - 变量
first,我猜,是用来表示我们是在播放第一首歌还是栈中还有歌曲。任一情况都可以从clips栈和current索引中检查到,因此这个变量并不需要。 - 变量
numberofClips可以省略,因为它总是反映clips.size(),所以在需要使用numberofClips的地方,只要改用clips.size()即可。 - 变量
next和prev可以省略,因为它们总是等于current加一或减一,因此我们始终可以分别改用current + 1和current - 1,以避免始终让它们与current同步。
所以关于所假定的最小可复现示例的核心问题在于 current 的数值在加载/播放/暂停歌曲时没有被正确赋值(这导致了 ArrayIndexOutOfBoundsException),以及播放器状态检查条件(导致原本应执行的代码未被执行)。
修复上述问题以及一些其他的小改动后,播放器就能工作。以下是可工作的代码(最小改动):
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Stack;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
public class Main {
public static class MusicPlayerGui extends JFrame implements ActionListener {
JMenu songMenu;
JMenuItem loadMusic;
JMenuItem loadPlaylist;
// Button
JPanel playbackBtns;
JButton prevButton;
JButton playButton;
JButton pauseButton;
JButton nextButton;
// Music stuff
Stack<Clip> clips = new Stack<>();
//int next; //Removed variable.
//int prev; //Removed variable.
int current = 0;
float frameRate;
//int numberofClips; //Removed variable.
//boolean play = true; //Removed variable.
//boolean first = true; //Removed variable.
boolean playlist;
public MusicPlayerGui() {
super("Music Player");
setSize(400, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
//setLayout(null);
setLayout(new GridLayout(0, 1));
//getContentPane().setBackground(BG_Color);
// Ignore this
addUiComponents();
}
private void addUiComponents() {
addToolbar();
addPlaybackBtns();
// removed the rest of the function
}
//Modified method.
private void addToolbar() {
//toolbar = new JToolBar();
//toolbar.setBounds(0, 0, getWidth(), 30);
//toolbar.setFloatable(false);
//add(toolbar);
//menuBar = new JMenuBar();
//toolbar.add(menuBar);
final JMenuBar menuBar = new JMenuBar();
super.setJMenuBar(menuBar);
songMenu = new JMenu("Song");
menuBar.add(songMenu);
loadMusic = new JMenuItem("Load music from directory");
loadMusic.addActionListener(this);
songMenu.add(loadMusic);
loadPlaylist = new JMenuItem("Load playlist from directory");
loadPlaylist.addActionListener(this);
songMenu.add(loadPlaylist);
}
//Assumed method body...
private void addPlaybackBtns() {
playbackBtns = new JPanel();
super.add(playbackBtns);
prevButton = new JButton("Previous");
prevButton.addActionListener(this);
prevButton.setEnabled(false);
playbackBtns.add(prevButton);
playButton = new JButton("Play");
playButton.addActionListener(this);
playButton.setEnabled(false);
playbackBtns.add(playButton);
pauseButton = new JButton("Pause");
pauseButton.addActionListener(this);
pauseButton.setEnabled(false);
playbackBtns.add(pauseButton);
nextButton = new JButton("Next");
nextButton.addActionListener(this);
nextButton.setEnabled(false);
playbackBtns.add(nextButton);
}
//Assumed method body...
private void enablePlayButton() {
playButton.setEnabled(true);
pauseButton.setEnabled(false);
}
//Assumed method body...
private void enablePauseButton() {
playButton.setEnabled(false);
pauseButton.setEnabled(true);
}
//New method.
private void validateButtonsState() {
if (!clips.isEmpty()) {
prevButton.setEnabled(current - 1 >= 0);
nextButton.setEnabled(current + 1 < clips.size());
if (current >= 0 && current < clips.size()) {
playButton.setEnabled(!clips.get(current).isActive());
pauseButton.setEnabled(clips.get(current).isActive());
}
else {
playButton.setEnabled(false);
pauseButton.setEnabled(false);
}
}
else {
playButton.setEnabled(false);
pauseButton.setEnabled(false);
prevButton.setEnabled(false);
nextButton.setEnabled(false);
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == loadMusic) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("Music Files", "wav"));
int response = fileChooser.showOpenDialog(null);
if (response == JFileChooser.APPROVE_OPTION) {
File file = new File(fileChooser.getSelectedFile().getAbsolutePath());
try (AudioInputStream aS = AudioSystem.getAudioInputStream(file)) {
if (!clips.isEmpty() && clips.get(current).isActive()) {
//enablePlayButton(); //Not needed, since we added the 'validateButtonsState' call at the end of the listener body.
clips.get(current).stop();
}
//numberofClips++;
Clip clip = AudioSystem.getClip();
//current = numberofClips;
clip.open(aS);
clips.push(clip);
current = clips.size() - 1;
////'current' is updated, so 'next' and 'prev' should also be updated:
//prev = current - 1;
//next = current + 1;
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
ex.printStackTrace();
//System.out.println(ex);
}
}
}
if (e.getSource() == loadPlaylist) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int response = fileChooser.showOpenDialog(null);
if (response == JFileChooser.APPROVE_OPTION) {
File files = new File(fileChooser.getSelectedFile().getAbsolutePath());
for (File file : files.listFiles()) {
try (AudioInputStream aS = AudioSystem.getAudioInputStream(file)) {
if (!clips.isEmpty() && clips.get(current).isActive()) {
//enablePlayButton(); //Not needed, since we added the 'validateButtonsState' call at the end of the listener body.
clips.get(current).stop();
}
//numberofClips++;
Clip clip = AudioSystem.getClip();
playlist = true;
clip.open(aS);
clips.push(clip);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException ex) {
ex.printStackTrace();
//System.out.println(ex);
}
}
}
}
if (e.getSource() == playButton) {
enablePauseButton();
clips.get(current).start();
//next = current + 1;
//prev = current - 1;
///*You don't need the following commented code, since 'enablePauseButton()'
//is already called, but also the 'current' song is already playing. If you
//want to start the next song here, then you should have stopped the
//'current' one first (as in the action taken by 'prevButton' and 'nextButton')
//and then update 'next' and 'prev' indices.*/
//if (playlist) {
// if (!clips.isEmpty() && clips.get(current).isActive() && next < clips.size()) { //Fixed condition.
// enablePauseButton();
// clips.get(next).start();
// current = next;
// }
//}
}
if (e.getSource() == pauseButton) {
enablePlayButton();
clips.get(current).stop();
}
if (e.getSource() == prevButton) {
if (!clips.isEmpty() && current - 1 >= 0) { //Fixed condition.
if (clips.get(current).isActive()) {
enablePauseButton(); //Changed from enablePlayButton()... But this is not actually needed at all.
clips.get(current).stop();
clips.get(current - 1).start();
}
current = current - 1;
//next = current + 1;
//prev = current - 1;
}
}
//Assumed extra code:
if (e.getSource() == nextButton) {
if (!clips.isEmpty() && current + 1 < clips.size()) {
if (clips.get(current).isActive()) {
clips.get(current).stop();
clips.get(current + 1).start();
}
current = current + 1;
}
}
validateButtonsState();
}
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> new MusicPlayerGui().setVisible(true));
}
}
通过添加一个 [addLineListener] 的 LineListener 来检测歌曲结束,并据此重新验证播放与暂停按钮的 enabled 状态,可以进一步提升播放器的功能,但在调试给定代码时这并不关键。
有关如何使用Java Sound API的有用资源包括Java Tutorials,以及当前站点上 javasound 标签的信息。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。