Search code examples
javaswingjframejpanelvlcj

How to display a VLCj EmbeddedMediaPlayerComponent on top of JPanel in Java?


I was trying different libraries to play a video in my Java Swing game, I decided to use VLCj, I have a class called CutscenePlayer which is responsible for playing the videos but I also have a JPanel class called GamePanel and another class called CutsceneManager that calls the CutscenePlayer, whenever the video plays I can hear the sound but there's just a black screen, this is my CutscenePlayer class:

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

public class CutscenePlayer extends MediaPlayerEventAdapter {
    
    GamePanel gp;
    public static EmbeddedMediaPlayerComponent mediaPlayerComponent;
    public boolean playing;
    public String video;
    
    public CutscenePlayer(GamePanel gp) {
        this.gp = gp;
    }
    
    public void setCutscene(String filePath) {
        video = filePath;
    }
    public void playCutscene() {
        mediaPlayerComponent = new EmbeddedMediaPlayerComponent();
        
        Main.window.add(mediaPlayerComponent);
        
        mediaPlayerComponent.mediaPlayer().events().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
                @Override
                public void finished(MediaPlayer mediaPlayer) {
                    System.out.println("Hello");
                    mediaPlayerComponent.release();
                    gp.gameState = gp.cutsceneState;
                    playing = false;
                }
            }
        );
        
        mediaPlayerComponent.mediaPlayer().media().play(video);
    }
    public void play() {
        mediaPlayerComponent.mediaPlayer().controls().pause();
    }
    public void pause() {
        mediaPlayerComponent.mediaPlayer().controls().pause();
    }
} 

Here is the CutsceneManager code that calls the CutscenePlayer:

if (isCutscenePlaying == false) {
    gp.csPlayer.setCutscene("test3.mp4");
    gp.csPlayer.playCutscene();
    isCutscenePlaying = true;
    gp.gameState = gp.cutscenePlayingState;
}
else {
    scenePhase++;
    isCutscenePlaying = false;
}

And here is the GamePanel code that has the thread and draws everything:

@Override
public void run() {
final double drawInterval = 1000000000 / FPS;
        double delta = 0.0;
        long lastTime = System.nanoTime();
        long currentTime;
        
        while (gameThread != null) {
            currentTime = System.nanoTime();
            
            delta += (currentTime - lastTime) / drawInterval;
            lastTime = currentTime;
            while (delta >= 1.0) {
                if (gameState != cutscenePlayingState) {
                    update();
                    drawToTempScreen();
                    drawToScreen();
                }
                --delta;
            }
        }
    }
    public void update() {
        if (gameState == playState) {
            //PLAYER
            player.update();
            //NPC
            for (int i = 0; i < npc[1].length; i++) {
                if (npc[currentMap][i] != null) {
                    npc[currentMap][i].update();
                }
            }
            for (int i = 0; i < monster[1].length; i++) {
                if (monster[currentMap][i] != null) {
                    if (monster[currentMap][i].alive == true && monster[currentMap][i].dying == false) {
                        monster[currentMap][i].update();
                    }
                    if (monster[currentMap][i].alive == false) {
                        monster[currentMap][i].checkDrop();
                        monster[currentMap][i] = null;
                    }
                }
            }
            for (int i = 0; i < projectile[1].length; i++) {
                if (projectile[currentMap][i] != null) {
                    if (projectile[currentMap][i].alive == true) {
                        projectile[currentMap][i].update();
                    }
                    if (projectile[currentMap][i].alive == false) {
                        projectile[currentMap][i] = null;
                    }
                }
            }
            for (int i = 0; i < particleList.size(); i++) {
                if (particleList.get(i) != null) {
                    if (particleList.get(i).alive == true) {
                        particleList.get(i).update();
                    }
                    if (particleList.get(i).alive == false) {
                        particleList.remove(i);
                    }
                }
            }
            for (int i = 0; i < iTile[1].length; i++) {
                if (iTile[currentMap][i] != null) {
                    iTile[currentMap][i].update();
                }
            }
            eManager.update();
        }
        if (gameState == pauseState) {
            //nothing
        }
    }
    public void drawToTempScreen() {
        //DEBUG
        long drawStart = 0;
        if (keyH.debugOn == true) {
            drawStart = System.nanoTime();
        }
        
        //TITLE SCREEN
        if (gameState == titleState) {
            ui.draw(g2);
        }
//      //MAP SCREEN
//      else if (gameState == mapState){
//          map.drawFullMapScreen(g2);
//      }
        //OTHERS
        else {
            //TILE
            tileM.draw(g2);
            
            //INTERACTIVE TILE
            for (int i = 0; i < iTile[1].length; i++) {
                if (iTile[currentMap][i] != null) {
                    iTile[currentMap][i].draw(g2);
                }
            }
            
            //ADD ENTITIES TO THE LIST
            entityList.add(player);
            
            for (int i = 0; i < npc[1].length; i++) {
                if (npc[currentMap][i] != null ) {
                    entityList.add(npc[currentMap][i]);
                }
            }
            
            for (int i = 0; i < obj[1].length; i++ ) {
                if (obj[currentMap][i] != null) {
                    entityList.add(obj[currentMap][i]);
                }
            }
            
            for (int i = 0; i < monster[1].length; i++ ) {
                if (monster[currentMap][i] != null) {
                    entityList.add(monster[currentMap][i]);
                }
            }
            
            for (int i = 0; i < projectile[1].length; i++ ) {
                if (projectile[currentMap][i] != null) {
                    entityList.add(projectile[currentMap][i]);
                }
            }
            for (int i = 0; i < particleList.size(); i++ ) {
                if (particleList.get(i) != null) {
                    entityList.add(particleList.get(i));
                }
            }
            //SORT
            Collections.sort(entityList, new Comparator<Entity>() {
                
                @Override
                public int compare(Entity e1, Entity e2) {
                    int result = Integer.compare(e1.worldY, e2.worldY);
                    return result;
                }
            });
            
            //DRAW ENTITIES
            for (int i = 0; i < entityList.size(); i++) {
                entityList.get(i).draw(g2);;
            }
            //EMPTY ENTITY LIST
            entityList.clear();
            
            //ENVIORNMENT
            eManager.draw(g2);
            
//          //MINIMAP
//          map.drawMinimap(g2);
            
            //CUTSCENE MANAGER
            csManager.draw(g2);
            
            //UI
            ui.draw(g2);
            
            //TIME AMULET SCREEN
            if (gameState == timeAmuletState){
                g2.setColor(new Color(0, 0, 0, 150));
                g2.fillRect(0, 0, screenWidth, screenHeight);
                dClock.drawClock(g2);
            }
            
        }
        
        //DEBUG
        if (keyH.debugOn == true) {
            long drawEnd = System.nanoTime();
            long passed = drawEnd - drawStart;
            System.out.println("Draw Time: " + passed);
            
            g2.setFont(getFont().deriveFont(20F));
            g2.setColor(Color.WHITE);
            int x = 10;
            int y = 400;
            int lineHeight = 20;
            
            g2.drawString("Draw Time: " + passed, x, y); y += lineHeight;
            g2.drawString("Col: " + (player.worldX + player.solidArea.x)/tileSize, x, y); y += lineHeight;
            g2.drawString("Row: " + (player.worldY + player.solidArea.y)/tileSize, x, y); y += lineHeight;
            g2.drawString("God Mode: "+ keyH.godModeOn, x, y); //y += lineHeight;
        }
    }
    public void drawToScreen() {
        if (gameState != cutscenePlayingState) {
            Graphics g = getGraphics();
            g.drawImage(tempScreen, 0, 0, screenWidth2, screenHeight2, null);
            g.dispose();
        }
    }

And lastly, the Main class:

public static void main(String[] args) {
        window = new JFrame();
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setResizable(false);
        window.setTitle("Time Maze");
        new Main().setIcon();
        
        window.add(gamePanel);

        gamePanel.config.loadConfig();
        if (gamePanel.fullScreenOn) {
            window.setUndecorated(true);
        }

        window.pack();

        window.setLocationRelativeTo(null);
        window.setVisible(true);
        
        gamePanel.setUpGame();
        gamePanel.startGameThread();
}

I wanted it to play the video and then resume to the GamePanel so that the game can be continued, but instead I get a black screen (the color of the GamePanel background), and the game won't even resume. The problem is that the GamePanel is displayed over the mediaPlayerComponent. I tried setContentPane on the mediaPlayerComponent but after the video finishes the screen will be stuck on the last frame of the video.


Solution

  • I replaced the playCutscene() method with this:

    public void playCutscene() {
        mediaPlayerComponent = new EmbeddedMediaPlayerComponent();
        Main.window.add(mediaPlayerComponent);
        Main.gamePanel.setVisible(false);
        playing = true;
        
        mediaPlayerComponent.mediaPlayer().events().addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
                @Override
                public void finished(MediaPlayer mediaPlayer) {
                    System.out.println("Hello");
                    Main.window.remove(mediaPlayerComponent);
                    Main.gamePanel.setVisible(true);
                    gp.gameState = gp.cutsceneState;
                    playing = false;
    
                    //mediaPlayerComponent.release();
                }
            }
        );
        //RELEASE RESOURCES ONLY WHEN EXITING APPLICATION,
        //OTHERWISE THERE'S A CHANCE OF PROGRAM TERMINATING
        mediaPlayerComponent.mediaPlayer().media().play(video);
    }
    

    It works by adding the mediaPlayerComponent to the window and then setting the GamePanel to not visible. At the end of the video it sets it back to normal and releases the resources.