Caprica Software

vlcj 3.x Tutorial

Basic Events

Building on previous tutorials, we will now add some basic media player event handlers.


Background Information

The most important thing to realise with media player events is that they are executed in the context of a background thread. This means that you must not update any user interface state directly inside one of your event handlers. You can update user interface state if you use SwingUtilities.invokeLater() - this is not specific to vlcj, this a common use-case for multi-threaded Swing applications in general.

The next thing to realise with media player events is more technical and you usually will not care about it - it is however worth mentioning: ordinarily with LibVLC it is not possible to invoke a method on the media player from inside an event handler, it will cause a dead-lock if you try it. This is a severe constraint because it is often a very useful thing to do.

There is no such constraint in vlcj, your event handlers are free to invoke methods on the media player. This is possible because vlcj guarantees that all native events are delivered on an application thread (not the native thead), and more than this vlcj guarantees that all native events received will be delivered to listeners serially and in the order that they were received.

These two things together can greatly simplify your application's use of media player events and allow you to reason more easily about the threading behaviour of your application.


Let's Get Started

You should now already have a basic template for how to create a vlcj application, so this tutorial will no longer duplicate all of the code each time - instead we'll just show the new code fragments.


Adding an Event Listener

Media player event listeners are similar to regular Swing event listeners. You simply add an instance of a MediaPlayerEventListener to your media player.

In common with many other Swing event listeners, an equivalent adapter class is provided with default empty implementations for all of the media player event listeners. Your application need then extend (via sub-classing) MediaPlayerEventAdapter and provide bespoke implementations for only the events you are interested in.

We'll start by adding our listener using an empty adapter implementation, and fill in methods for the events we're intersted in later. The listener is added before the frame is shown.

mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(
@Override
new MediaPlayerEventAdapter() {
}
);
frame.setContentPane(contentPane);
frame.setVisible(true);

You can add as many different event listeners as you want. This can be useful when you have different components needing to act on different events.


Add Event Handlers

The basic events usually of interest to all applications are playing, finished and error. There are numerous other events but we will cover just those basic events here:

mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void playing(MediaPlayer mediaPlayer) {
}

@Override
public void finished(MediaPlayer mediaPlayer) {
}

@Override
public void error(MediaPlayer mediaPlayer) {
}
});

Using Template Methods

The EmbeddedMediaPlayerComponent also provides template methods for all media player events. You may find it more convenient to create a sub-class of EmbeddedMediaPlayerComponent and override the methods you are interested in.

This fragment shows the equivalent template method usage to the listener added previously:

mediaPlayerComponent = new EmbeddedMediaPlayerComponent() {
@Override
public void playing(MediaPlayer mediaPlayer) {
}

@Override
public void finished(MediaPlayer mediaPlayer) {
}

@Override
public void error(MediaPlayer mediaPlayer) {
}
};

It is up to you to choose which method - either one, other or both of adding listeners and overriding template methods.


Add Event Handler Implementations

The playing event is raised when the media starts playback.

Here we get the media meta data to get the title. We then change the title of the application frame to show this media title:

@Override
public void playing(MediaPlayer mediaPlayer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.setTitle(String.format(
"My First Media Player - %s",
mediaPlayerComponent.getMediaPlayer().getMediaMeta().getTitle()
));
}
});
}

The finished notification is raised when the media has finished playback normally.

Here we simply close the main frame and exit the application:

@Override
public void finished(MediaPlayer mediaPlayer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
closeWindow();
}
});
}

The error notification is somewhat more interesting. When playing media it is actually an asynchronous method call - the media player playMedia method will return immediately and LibVLC will asynchronously try and start playing the media. In practical terms, you won't immediately get a success/error indicator until some time later. The way you get the success/error indicator is via the corresponding media player events, i.e. playing or error.

In this tutorial, we'll display a standard error dialog in response to an error, before closing the main frame and exiting the application:

@Override
public void error(MediaPlayer mediaPlayer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(
frame,
"Failed to play media",
"Error",
JOptionPane.ERROR_MESSAGE
);
closeWindow();
}
});
}

All of these event handler implementations update user interface state so must be executed on the Swing Event Dispatch Thread via SwingUtilities.invokeLater

  • in your own application this may not always be the case for certain events.

The closeWindow method is just a simple private helper method that raises a WINDOW_CLOSING event as though the user had closed the main application frame themselves. This allows us to consolidate the clean-up handling code in one place.

private void closeWindow() {
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
}

Summary

Here is the finished code:

Tutorial.java
package tutorial;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.discovery.NativeDiscovery;
import uk.co.caprica.vlcj.player.MediaPlayer;
import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter;

public class Tutorial {

private final JFrame frame;

private final EmbeddedMediaPlayerComponent mediaPlayerComponent;

private final JButton pauseButton;

private final JButton rewindButton;

private final JButton skipButton;

public static void main(final String[] args) {
new NativeDiscovery().discover();

SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Tutorial(args);
}
});
}

public Tutorial(String[] args) {
frame = new JFrame("My First Media Player");
frame.setBounds(100, 100, 600, 400);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.out.println(e);
mediaPlayerComponent.release();
System.exit(0);
}
});

JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());

mediaPlayerComponent = new EmbeddedMediaPlayerComponent();
contentPane.add(mediaPlayerComponent, BorderLayout.CENTER);

JPanel controlsPane = new JPanel();

pauseButton = new JButton("Pause");
controlsPane.add(pauseButton);

rewindButton = new JButton("Rewind");
controlsPane.add(rewindButton);

skipButton = new JButton("Skip");
controlsPane.add(skipButton);

contentPane.add(controlsPane, BorderLayout.SOUTH);

pauseButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mediaPlayerComponent.getMediaPlayer().pause();
}
});

rewindButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mediaPlayerComponent.getMediaPlayer().skip(-10000);
}
});

skipButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mediaPlayerComponent.getMediaPlayer().skip(10000);
}
});

mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void playing(MediaPlayer mediaPlayer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.setTitle(String.format(
"My First Media Player - %s",
mediaPlayerComponent.getMediaPlayer().getMediaMeta().getTitle()
));
}
});
}

@Override
public void finished(MediaPlayer mediaPlayer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
closeWindow();
}
});
}

@Override
public void error(MediaPlayer mediaPlayer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(
frame,
"Failed to play media",
"Error",
JOptionPane.ERROR_MESSAGE
);
closeWindow();
}
});
}
});

frame.setContentPane(contentPane);
frame.setVisible(true);

mediaPlayerComponent.getMediaPlayer().playMedia(args[0]);
}

private void closeWindow() {
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
}
}

This tutorial has shown how to hook up media player event listeners. The actual vlcj code is minimal and very simple. Most of the code deals with updating the user interface state and is standard Swing code.