Actions stop working in Java

可紊 提交于 2019-12-25 08:25:33

问题


so this is my very first post and i am really sorry if my grammar is bad (english is not my mother language). I recently started programming in java and i am interested in learning. So i started a few small projects to help me understand more of the basic stuff and to improve my coding.

Recently i read about keyListeners, keyBindings and all that stuff. So i thought i code a very basic program (nearly no gui) which should work like a simple piano:

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import java.awt.*;
import java.io.File;
import javax.swing.*;

import java.awt.event.*;

public class pianl1 {


    public static void main (String[] args){
        int i= 0;

        File C7 = new File("res/39191__jobro__piano-ff-044.wav");
        File D7 = new File ("res/39194__jobro__piano-ff-046.wav");

        JLabel lab1 =new JLabel("Hallo");
        frame.add(lab1);


        AbstractAction keyBindingReactorC7 = new AbstractAction(){
            @Override
            public void actionPerformed(ActionEvent e){
                Playsound(C7);
            }
        };
        AbstractAction keyBindingReactorD7 = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Playsound(D7);
            }

        JPanel panel= new JPanel(new FlowLayout());
        frame.getContentPane().add(panel);
        frame.pack();

        panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("C"),
                "cPressed");
        panel.getActionMap().put("cPressed", keyBindingReactorC7);

        panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("D"),
                "dPressed");
        panel.getActionMap().put("dPressed", keyBindingReactorD7);


    public static void Playsound(File Sound){
        try{
            Clip clip = AudioSystem.getClip();
            clip.open(AudioSystem.getAudioInputStream(Sound));
            clip.start();

        }catch(Exception e){}

    }

}

And it is working fine. If i press the keys, it plays the sound, and even if i hammer the keys the sound has almost no delay. Which is exactly what i wanted.

But there is a small error.

If i keep pressing one key, or i press two keys at the same time (which works, it plays both at the same time), it sometimes happens, that there will be no sound at all. I can't do anything to get it to work again and all i can do is restart the whole program. This happens randomly and i cant figure out why. I know that something is blocking my actions from working continuously.


回答1:


The answer to your question revolves around what you ultimately want to achieve, if you simply want to stop more than one sound playing, then you need some kind of condition you can monitor, if you want to stop a "particular" sound from playing, then you need to maintain some kind of List of flags which you can check.

So, based on your code, lets assume you're making a piano, so you want sounds to overlap, in this case, we need some way to determine if a particular sound is been played or not.

There are a number of ways to achieve this, but lets settle on a Set, which allows you to maintain unique a List of items (no duplicates).

Each time playSound is called, we check this list and determine if the sounds is already been played or not, if it isn't, we add the sound to the list and play it, when the sound stops, we remove it from the list

At the core...

I've changed your code a bit and I'll explain it more it detail later, but essentially, this is the "core" of the idea...

private Set<File> playing = new HashSet<File>(25);

public void playsound(File sound) {
    try {
        // Is the been played or not?
        if (!playing.contains(sound)) {
            // And the sound to prevent it from been played again
            playing.add(sound);
            // Set up a new clip
            Clip clip = AudioSystem.getClip();
            // Monitor the clip's status, we want to know when it ends
            clip.addLineListener(new LineListener() {
                @Override
                public void update(LineEvent event) {
                    // Clip has stopped
                    if (event.getType() == LineEvent.Type.STOP) {
                        // Release the resources
                        clip.close();
                        // Remove the sound from our list
                        // so it can be played again
                        playing.remove(sound);
                    }
                }
            });
            // Play it again Sam
            clip.open(AudioSystem.getAudioInputStream(sound));
            clip.start();
        }
    } catch (Exception e) {
        // Remove the sound if something goes wrong
        playing.remove(sound);
        e.printStackTrace();
    }

}

Runnable example

Okay, so I've "updated" your code a little, here's why...

  • Java has some established coding conventions all developers are encouraged to follow, they make it easier for other people to read your code and for you to read theirs, take a look at at Code Conventions for the Java TM Programming Language for more details. Having said, I made all your variables start with a lowercase character
  • A created a custom panel. Okay, this is just me, but it makes life a lot easier as you can more easily encapsulate the logic you are trying to implement. Because you're making use of the Action API, another choice would have been to create a SoundManager class, which contained the logic for managing and playing the sounds and passed a reference to each Action, personally, I'd probably end up with both
  • I've used EventQueue.invokeLater to ensure that the UI is created within the context of the Event Dispatching Thread, reducing any possible risk of violating the single threaded nature of Swing, always a good idea ;)

You seem to be off to a good start trying things, well done, keep it up!

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

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 {

        public TestPane() {

            File c7 = new File("res/res39191__jobro__piano-ff-044.wav");
            File d7 = new File("res/39194__jobro__piano-ff-046.wav");

            add(new JLabel("Hello"));

            AbstractAction keyBindingReactorC7 = new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    playsound(c7);
                }
            };
            AbstractAction keyBindingReactorD7 = new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    playsound(d7);
                }
            };
            getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("C"),
                                                                                                                             "cPressed");
            getActionMap().put("cPressed", keyBindingReactorC7);

            getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("D"),
                                                                                                                             "dPressed");
            getActionMap().put("dPressed", keyBindingReactorD7);
        }

        private Set<File> playing = new HashSet<File>(25);

        public void playsound(File sound) {
            try {
                if (!playing.contains(sound)) {
                    playing.add(sound);
                    Clip clip = AudioSystem.getClip();
                    clip.addLineListener(new LineListener() {
                        @Override
                        public void update(LineEvent event) {
                            if (event.getType() == LineEvent.Type.STOP) {
                                clip.close();
                                playing.remove(sound);
                            }
                        }
                    });
                    clip.open(AudioSystem.getAudioInputStream(sound));
                    clip.start();
                }
            } catch (Exception e) {
                playing.remove(sound);
                e.printStackTrace();
            }

        }
    }
}

Ok, but how do I make it so only one sound can play at a time?

Ah, good question...

Basically, we use a single clip and check it's state...

private Clip clip;

public void playsound(File sound) {
    try {
        if (clip == null) {
            clip = AudioSystem.getClip();
            clip.addLineListener(new LineListener() {
                @Override
                public void update(LineEvent event) {
                    if (event.getType() == LineEvent.Type.STOP) {
                        clip.close();
                    }
                }
            });
        }
        // Is the clip active or running?
        if (!clip.isActive() && !clip.isRunning()) {
            if (clip.isOpen()) {
                clip.close();
            }
            clip.open(AudioSystem.getAudioInputStream(sound));
            clip.start();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Now, my audio experience with Java is pretty non-existent, but some things I might consider trying...

  • Make a AudioManager or SoundManager class, which manages the sounds and provides a simple interface for playing them (like playC, playD). Pre-load the clips for each sound. This is going to require some additional management, because when the clip ends, you will need to "reset" it back to the start. This "should" make it faster to play the clips, but it will probably consume more resources, which trade off is more important to you? You could also create some kind of cache, where if a clip isn't played for a period of time, it's closed and disposed of.
  • Keep playing a sound till a key is released...nice challenge for you ;)



回答2:


In addition to the great help of @MadProgrammer, i also noticed something else. I work on Mac, but i also own Windows-machine. I just recently switched to Mac and i didn't notice (until now) that, if you hold down a key long enough, OSX will automatically show more key-options (stuff like û,ć and all that). So i switched everything to my Windows-machine and voilá, it works. Of course there are some minor bugs, but in the end i am pretty happy with the result.



来源:https://stackoverflow.com/questions/42171398/actions-stop-working-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!