caprica / vlcj

Java framework for the vlc media player
http://www.capricasoftware.co.uk/projects/vlcj
1.13k stars 260 forks source link

Switching to another media is partly possible using either MediaPlayer or MediaListPlayer #1172

Closed nemezz9 closed 1 year ago

nemezz9 commented 2 years ago

Problem: creating player once and loading media one after each other is not possible in certain cases.

It works fine when all files are located locally. Nothing weird happens in this case.

When it comes to a specific setups, the problem might appear.

In my case I am trying to play media files, which are located on a separated NAS (QNAS). The files are of format .mxf. Java 14 is used. (AdoptOpenJDK) VLC tested: 3.0.11-3.0.17.4 VLCj tested: 4.5.2 - 4.8.2 (where 4.5.2 was a bit more stable, but seems a lucky case) I have tried both: MediaPlayer and MediaListPlayer. Other details: the worse behaved Windows 10 VM installed on MacOS via parallels. It just become unworking after first item load.

The first load (just after instantiation) of ANY media file is successful. All the next item loads are causing issues. What I noticed is that using MediaPlayer, after preparing the next file with: this.mediaPlayer.media().prepare(file.getAbsolutePath()); I will endlessly receive, probably in a some kind of a loop, the "finished" and "stopped" events. Listening them like: mediaPlayer.events().addMediaPlayerEventListener(this.eventListener);

The only thing helps - full re-creation of player instances.

Is there any hints, maybe I am missing something ?

Let me know, if any of inputs are also important. Will really appreciate your help.

caprica commented 2 years ago

How are you accessing the NAS?

You map a drive? SMB or something?

caprica commented 2 years ago

Run with increased VLC log verbosity and examine logs, try "-vvv", "-vv", or "-v" when you create a MediaPlayerFactory.

nemezz9 commented 2 years ago

How are you accessing the NAS?

You map a drive? SMB or something?

I have tried both: mapping NAS as a drive "M:/..." and direct local network access. "//nasname/..." - by personal feeling mapped as a drive had worse behavior, but the issue keep existing in both cases.

Run with increased VLC log verbosity and examine logs, try "-vvv", "-vv", or "-v" when you create a MediaPlayerFactory.

Will try ASAP and let you know!

caprica commented 2 years ago

Also, what happens if you play a playlist of these files in VLC itself?

nemezz9 commented 2 years ago

Also, what happens if you play a playlist of these files in VLC itself?

I am back with more details.

Setup: AdoptOpenJDK 14.0.12+12 GC - Shenandoah GC Option is -XX:ShenandoahGCHeuristics=compact VLC probably could be any subversion of 3.0.x (but tested on 3.0.12+) vlcj 4.5.2+

Actually the GC option mentioned above is causing the player not to play an item. Also: no logs appeared with option "-vvv" after the start of the failed item.

Not sure if its directly related to the vlcj itself.

caprica commented 2 years ago

Are you sure you're keeping all your vlcj objects alive?

Maybe post a minimal sample.

caprica commented 2 years ago

Also, did you actually try adding all your network files to a playlist in VLC itself and seeing what happened.

nemezz9 commented 2 years ago

Are you sure you're keeping all your vlcj objects alive?

Maybe post a minimal sample.

Completely sure, that all objects are alive. As far as this was a special scenario, which was not the case before. Previously all files were located locally/network drive (windows machine)/nas - had no matter. After migration to 14 everything went fine too, but tuning the GC did the "magic".

On one physical Windows 10 machine (not a VM), connected to the NAS, I managed to solve the issue by turning off Heuristics for Shenandoah GC. Another case I had a Windows 10 VM on a mac os via paralells also with turned on Shenandoah - the solution was to turn it off completely.

I have researched that this GC had some bugs in Java 14, as far as it was still experimental feature.

Also, did you actually try adding all your network files to a playlist in VLC itself and seeing what happened.

VLC does fine in this case, no issues.

So short summary: it seems that GC is over-collecting some natives (maybe) or other VLCj related stuff by itself.

Another solution which also worked was: re-creating all the instances of player (factory, player, list, etc)

I have to try the same scenario with a newer JDK, probably 17th or higher non-LTS one. And I will let you know.

caprica commented 2 years ago

If you're holding your object references correctly, then the GC should not be collecting them. I've played many, many test media using media players and media list players, and in all cases if I keep hold of my objects I don't see any weirdness. I even ran tests with Shenandoah and I saw no problems (using JDK 18).

And why it would make any difference playing local files vs NAS files makes no sense either.

nemezz9 commented 2 years ago

As I said before, I have tried other setups (PCs + Network drives, NAS's, local files), which had no such issues, even with the activated GC. It happens only in this special scenario. Here is a simple sample, probably just the same as per examples.

public class VLCJPlayer {

    private static final String BLACK_BACKGROUND_STYLE = "-fx-background-color: rgb(0, 0, 0);";
    private final Canvas canvas;
    private final MediaPlayerFactory mediaPlayerFactory;
    private final EmbeddedMediaPlayer mediaPlayer;
    private final MediaList mediaList;
    private final MediaListPlayer mediaListPlayer;
    private final WritablePixelFormat<ByteBuffer> pixelFormat;

    public VLCJPlayer() {
        this.canvas = new Canvas();
        this.canvas.setStyle(BLACK_BACKGROUND_STYLE);

        this.pixelFormat = PixelFormat.getByteBgraPreInstance();

        this.mediaPlayerFactory = new MediaPlayerFactory();
        this.mediaPlayer = this.mediaPlayerFactory.mediaPlayers().newEmbeddedMediaPlayer();
        this.mediaPlayer.videoSurface().set(new JavaFxVideoSurface());

        this.mediaList = this.mediaPlayerFactory.media().newMediaList();
        this.mediaListPlayer = this.mediaPlayerFactory.mediaPlayers().newMediaListPlayer();

        this.mediaListPlayer.mediaPlayer().setMediaPlayer(this.mediaPlayer);
        this.mediaListPlayer.list().setMediaList(this.mediaList.newMediaListRef());
    }

    public void loadVideo(File file, long previewPosition) {
        if (file != null && file.exists()) {
            LOGGER.info("Trying to load media: {}", file.getAbsolutePath());

            getMediaPlayer().submit(() -> {
                this.mediaList.media().clear();
                this.mediaList.media().add(file.getAbsolutePath());
            });

            /*this.mediaPlayer.submit(() -> {
                this.mediaPlayer.media().prepare(file.getAbsolutePath());
            });*/
        }
    }

    protected void startVideoPlayback() {
        if (img != null) {
            pixelWriter.set(img.getPixelWriter());
        }
        VideoPlayer.State state = getState();
        getMediaPlayer().submit(() -> {
            if(state.equals(VideoPlayer.State.PAUSED)){
                getMediaPlayer().controls().play();
            } else {
                getMediaPlayer().controls().playNext();
            }
        });
    }

    protected void pauseVideoPlayback() {
        getMediaPlayer().submit(() -> {
            getMediaPlayer().controls().pause();
        });
    }

    protected void stopVideoPlayback() {
        getMediaPlayer().submit(() -> {
            getMediaPlayer().controls().stop();
        });
    }

}
caprica commented 2 years ago

I know you said it before, but if things are constructed correctly, then why should a different garbage collector make any difference? It shouldn't.

What keeps your VLCJPlayer instance alive?

nemezz9 commented 2 years ago

I know you said it before, but if things are constructed correctly, then why should a different garbage collector make any difference? It shouldn't.

What keeps your VLCJPlayer instance alive?

A simple Singleton dialog, which is kept in memory.

VLCJPlayer instance is a final variable in a singleton class. Here is an example

`public class VideoComponentController {

private static VideoComponentController instance;

public static getInstance() {
   // ...
}

private final VideoComponentView view;
private final VLC4JPlayer player;

private VideoComponentController() { this.player = new VLC4JPlayer(); }

// actions of play/pause/loading, etc

`

caprica commented 2 years ago

It's too difficult to say based on these fragments.

Maybe the GC aspect to this is a red herring, but switching the GC should absolutely have no bearing on the result if the code is all structured correctly.

My own tests, with or without Shenadoah, show no issues with my code. If you find something, by all means point it out.

nemezz9 commented 2 years ago

Well, I also have no issues using my own pc. I am also using ffmpeg frame grabber, to have precise frame-seeking. Maybe concurrent access to the file is causing issues, but on the other hand, the first item being loaded to the player is performing just fine. What I also do: when needed, I am trying to seek the player from the position of zero to a specific place (In-point), maybe seeking is a real problem ? What I also noticed, is that position callback working with a kind of delay on my test-setup, its not immediate and could not be updated for a long time (5+ seconds, if not more).

caprica commented 1 year ago

I don't know, this seems to not be a problem with vlcj itself to be honest.

If you find any concrete evidence by all means post it here and I'll reopen it.