javacv
javacv copied to clipboard
Mouse events kill key events in CanvasFrame
First: thanks for the amazing library!
I'm trying to make a video player with controls from both keyboard and mouse with spacebar or mouse click. This works with the spacebar or mouse click to stop/start the video. You can use the spacebar as many times as you like at first, but after the first mouse click the spacebar no longer works.
I have tried various ways to attach/re-attach the key and mouse event listeners but it is beyond me! Can you help? Here is the code to replicate:
public class MouseEventsKillKeyEventsPlayerTest { private static class PlayerThread extends Thread { private PlayerCanvasFrame _playerCanvasFrame; private boolean _running; private double _frameRate; public boolean getRunning() { return _running; } public PlayerThread(PlayerCanvasFrame sessionFrame) { // retain the session frame _playerCanvasFrame = sessionFrame; // mark running _running = true; // start with frameRate from video frameRate from grabber _frameRate = _playerCanvasFrame.getFrameGrabber().getFrameRate(); start(); } @Override public void run() { // record milliseconds for update image to determine any sleep to match frame rate long startTime = 0; long finishTime = System.currentTimeMillis(); long sleepTime = 0; while (_running) { // get the time now startTime = finishTime; // update the frame, passing in the sleep time for skipping when fast _playerCanvasFrame.updateImage(sleepTime); // get the finish time finishTime = System.currentTimeMillis(); // derive any sleep time. Positive times are sleep times, negative times are skip. Never more than 2 seconds sleepTime = Math.min(2000, (long) (1000 / _frameRate - (finishTime - startTime))); // check sleep if need be, or skip showing until if (sleepTime > 0) { // do any sleep try { Thread.sleep(sleepTime); } catch (InterruptedException e) {} } } } @Override public void interrupt() { _running = false; super.interrupt(); } } public static class PlayerCanvasFrame extends CanvasFrame { private File _file; private FFmpegFrameGrabber _grabber; private PlayerThread _playerThread; public PlayerCanvasFrame(File file) { super("Viewer"); PlayerCanvasFrame playerCanvasFrame = this; // a key listener on this frame to stop/start, and quit KeyListener keyListener = new KeyListener() { @Override public void keyPressed(KeyEvent ev) { log("Key event " + ev); int keyCode = ev.getKeyCode(); if (keyCode == KeyEvent.VK_SPACE) { // stop or start the player togglePlayer(); } else if (keyCode == KeyEvent.VK_X || keyCode == KeyEvent.VK_Q || keyCode == KeyEvent.VK_ESCAPE) { quit(); } } @Override public void keyReleased(KeyEvent ev) {} @Override public void keyTyped(KeyEvent ev) {} }; // add the keyListener to the frame. Mouse events turn it off for some reason, hope was having it like this we could add it back playerCanvasFrame.addKeyListener(keyListener); // I've read the mouse listener goes on the canvas getCanvas().addMouseListener(new MouseListener() { // But we also get mouse events and loss of key events after a click with it on the frame? //addMouseListener(new MouseListener() { @Override public void mousePressed(MouseEvent ev) { log("Mouse event " + ev); } @Override public void mouseReleased(MouseEvent ev) { log("Mouse event " + ev); } @Override public void mouseClicked(MouseEvent ev) { log("Mouse event " + ev); // stop or start the player togglePlayer(); // this fires last (after the two above), after which keyListener gets no more events. Try adding keyListener back? playerCanvasFrame.addKeyListener(keyListener); } @Override public void mouseEntered(MouseEvent ev) { // this does not kill the keyListener... log("Mouse event " + ev); } @Override public void mouseExited(MouseEvent ev) { // this does not kill the keyListener... log("Mouse event " + ev); } }); // set up and show frame setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setFocusable(true); setVisible(true); // load the file! loadFile(file); } public FrameGrabber getFrameGrabber() { return _grabber; } public void togglePlayer() { if (_playerThread == null || !_playerThread.getRunning()) { _playerThread = new PlayerThread(this); } else { stopPlayer(); } } public void stopPlayer() { if (_playerThread != null) { _playerThread.interrupt(); _playerThread = null; } } public void quit() { stopPlayer(); if (_grabber != null) try { _grabber.stop(); } catch (org.bytedeco.javacv.FFmpegFrameGrabber.Exception e) { log("Error stopping grabber : " + e.getMessage()); } // dispose the frame dispose(); // kill process System.exit(0); } public void updateImage(long sleepTime) { if (_file != null) { try { // set title to the name String title = _file.getName(); // if we have an open video reader if (_grabber != null) { // if already opened (we want to reuse it) if (_grabber.hasVideo()) { // grab the image frame (no audio) Frame frame = _grabber.grabImage(); // if we got a frame if (frame == null) { // stop any player stopPlayer(); } else { // show the frame, if there was 0 or some sleep (skip when negative) if (sleepTime >= 0) showImage(frame); } } // add the frame to the title for the video title += " : " + _grabber.getFrameNumber(); } // update title setTitle(title); } catch (java.lang.Exception ex) { log("Failed to update image : " + ex.getMessage()); } } } // overload to the above with no sleep public void updateImage() { updateImage(0); } // load file, create and start a grabber for it, show the first frame public void loadFile(File file) { log("Creating grabber for " + file); // retain file _file = file; // open a new grabber for this file _grabber = new FFmpegFrameGrabber(_file); try { log("Starting grabber..."); // start the grabber! _grabber.start(); // set the image size (just once) setSize(_grabber.getImageWidth(), _grabber.getImageHeight()); } catch (org.bytedeco.javacv.FFmpegFrameGrabber.Exception e) { log("Error starting grabber : " + e.getMessage()); } // update the image updateImage(); } } // very simple logging! public static void log(Object message) { System.out.println(message); } public static void main(String[] args) { log("Starting viewer..."); // Schedule a job for the event-dispatching thread creating and showing this application's GUI. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { log("Creating frame..."); // full path the video file we want to watch File file = new File("/path/to/movie.mp4"); // Create our player frame, it will set itself up new PlayerCanvasFrame(file); } }); } }
Many thanks!
I've gotten mouse and key events to play together by adding this after the getCanvas().addMouseListener(new MouseListener() { ... });
// this gives the focus back to the frame when the mouse events lose it so that key events are available again getCanvas().addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent arg0) { // request focus back on the frame to restore key listener playerCanvasFrame.requestFocus(); } @Override public void focusLost(FocusEvent arg0) {} });
But I'm not sure if this the intended/best solution!