Garbage Collection
Your application must keep hard references to the media player instances that you create. If you do not do this, your media player will become unexpectedly garbage collected at some future indterminate time and you will see a fatal crash eventually or immediately depending on how lucky you are.
Why?
A fatal crash occurs because in the background the media player that you created is running a native thread outside of your Java application and that native thread calls back in to a vlcj media player. If the vlcj media player has been garbage collected that native thread has nowhere to go so crashes.
You have to keep the hard reference to the media player because there is nothing else that keeps your media player reference alive - the native code knows nothing about Java applications.
When?
Consider this code:
public class BadCode {
public static void main(String[] args) {
new BadCode(args[0]);
try {
Thread.currentThread().join();
}
catch(InterruptedException e) {
}
}
public BadCode(String mrl) {
AudioMediaPlayerComponent mediaPlayerComponent = new AudioMediaPlayerComponent();
mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void finished(MediaPlayer mediaPlayer) {
exit(0);
}
@Override
public void error(MediaPlayer mediaPlayer) {
exit(1);
}
});
mediaPlayerComponent.getMediaPlayer().playMedia(mrl);
}
private void exit(int result) {
mediaPlayerComponent.release();
System.exit(result);
}
}You don't need to understand the whole thing right now, but look at where the
mediaPlayerComponent instance is created. It is a local variable inside the
constructor. This code will run and the media will indeed start playing. The
constructor will exit and the media player will keep playing for a while.
However, when the garbage collector runs there is nothing keeping the
mediaPlayerComponent reference alive - it is no longer "reachable" so the
media player instance is discarded and garbage collected even though it is
still running!
How to Fix?
public class BetterCode {
private final AudioMediaPlayerComponent mediaPlayerComponent;
public static void main(String[] args) {
new BetterCode(args[0]);
try {
Thread.currentThread().join();
}
catch(InterruptedException e) {
}
}
public BetterCode(String mrl) {
mediaPlayerComponent = new AudioMediaPlayerComponent();
mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void finished(MediaPlayer mediaPlayer) {
exit(0);
}
@Override
public void error(MediaPlayer mediaPlayer) {
exit(1);
}
});
mediaPlayerComponent().getMediaPlayer().playMedia(mrl);
}
private void exit(int result) {
mediaPlayerComponent.release();
System.exit(result);
}
}Here we have moved the mediaPlayerComponent from being a method-local
variable to being a class field variable. This means that the media player
instance will still be reachable after the constructor exits, and seemingly our
problem is solved.
But there is still a, less obvious, flaw with this code...
Look where the BetterCode instance is created - the instance is never
actually assigned to a variable. As before, the media will play for a while but
eventually (or maybe even shortly) the garbage collector will run and find that
the class instance itself is no longer reachable so will discard it - when that
happens, the mediaPlayerComponent instance in the class field variable also
becomes no longer reachable and so it too is discarded. The end result is a
fatal Java Virtual Machine crash.
How To Really Fix?
public class GoodCode {
private final AudioMediaPlayerComponent mediaPlayerComponent;
public static void main(String[] args) {
GoodCode goodCode = new GoodCode(args[0]);
try {
Thread.currentThread().join();
}
catch(InterruptedException e) {
}
}
public GoodCode(String mrl) {
mediaPlayerComponent = new AudioMediaPlayerComponent();
mediaPlayerComponent.getMediaPlayer().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
@Override
public void finished(MediaPlayer mediaPlayer) {
exit(0);
}
@Override
public void error(MediaPlayer mediaPlayer) {
exit(1);
}
});
mediaPlayerComponent.getMediaPlayer().playMedia(mrl);
}
private void exit(int result) {
mediaPlayerComponent.release();
System.exit(result);
}
}We must keep a reference to the class instance too, here the goodCode
variable retains a hard reference to our class instance.
Summary
This tutorial has explained the need for 'pinning' your media player instances in memory to prevent unwanted garbage collection.
There are different ways to achieve the same effect, do whatever works for your application, but the point is you must be aware of this.
Depending on your application you may not even have to do anything - for the
common case of using an EmbeddedMediaPlayerComponent (as discussed in this
series of tutorials so far) you usually have a JFrame instance that is
keeping a reference to the media player component instance (you added the media
player component to the frame) and this is enough to keep the instance alive
and prevent it from being garbage collected.
Depending on a number of factors, like your operating system, your Java Virtual Machine version, your application Java Virtual Machine memory configuration and garbage collection configuration you may encounter this problem immediately, a short time later, or some considerable time later lulling you into a false sense of security. You must be aware of this issue.