Skip to content

Instantly share code, notes, and snippets.

@rothso
Last active February 27, 2021 22:52
Show Gist options
  • Select an option

  • Save rothso/ae105ae538c72fff1b129dcb9ad2c50c to your computer and use it in GitHub Desktop.

Select an option

Save rothso/ae105ae538c72fff1b129dcb9ad2c50c to your computer and use it in GitHub Desktop.
Displaying a MediaView in JavaFX

Goal: Show a preview of your input and output videos in your JavaFX scene and become Asian Kid

Step 1

Integrate the code from Moussa. Here's the code in its raw form, without any modifications:

String MEDIA_URL = "https://liveexample.pearsoncmg.com/common/sample.mp4";

Media media = new Media(MEDIA_URL);
MediaPlayer mediaPlayer = new MediaPlayer(media);
MediaView mediaView = new MediaView(mediaPlayer);

Button playButton = new Button("PLAY");
playButton.setOnAction(e -> {
    mediaPlayer.play();
});

Pro tip: I suggest hand-typing this code (and generally any other snippets) as opposed to copying-pasting. It'll help you familiarize yourself with the difference between MediaPlayer and MediaView, which will come in handy later in this tutorial.

Where do I put this code?

Put this code anywhere in your start method. It's a good idea to put it after the line you've declared your root but before the line where you instantiated a new Scene(root, ...) because later we're gonna add some code that requires a reference to the root object.

I added the code and there are errors!

Pro tip: The red underlines are compile-time errors, which means you must fix them before the code can compile. You can hover over them to see Eclipse's suggestions on how to fix the code (like Microsoft Word's spellcheck). If you click any suggestion, Eclipse will automatically apply it for you.

In your editor, you'll see that the class names are red (as opposed to the variables or a method). That means Eclipse thinks these classes don't exist. Each class actually does exist, but you just haven't imported it yet. Eclipse can conveniently import it for you in the quick-fix-suggestion-tooltip:tm:. Be careful, though. Some classes may come up with multiple import choices because different packages contain a class with that particular name. Be sure to always choose the javafx option for this project or you'll get some weird errors! Do this for every "missing" class.

▶️ Does everything compile? Run the code! Verify that everything still works.

I ran the code but there's no video smh 😣

While Moussa's code creates the mediaView and playButton objects, he doesn't actually attach (add) any of them to a pane.

Pro tip: Each Node (e.g. buttons, images, text, and even other panes) must be connected to the root pane in order for it to be visible! If you instead add a Node to an intermediate pane, that pane must be added to the root pane (or another intermediate pane, in which case this pane must be added to the root pane—get the idea?). This creates a hierarchy where everything is eventually connected to the root.

Moussa intentionally left that part off, because different students will be using different layouts. The "add" method will be different for someone using a BorderPane than someone using a FlowPane or HBox, etc. Many people are using a GridPane, so we will proceed with code that uses a GridPane. If you aren't using a GridPane, just google "JavaFX How to add a node to X" to figure out what API to use.

Step 2

How can I add the MediaView to the GridPane?

Attach the mediaView object to your root GridPane:

root.add(mediaView, 0, 2, 3, 1);

This will put the mediaView at the coordinates (col=0, row=2) and give it colspan=3 and rowspan=1. Colspan and rowspan work similar to how you would merge cells in Excel. A colspan of 3 will merge the next 3 cells together, allowing that specific cell to be wider without stretching everything else in that column. Tweak these values if you'd like to produce a different arrangement.

How can I add the Button to the GridPane?

We can attach the playButton object in a similar fashion:

root.add(playButton, 3, 0);

Notice here that there is no colspan or rowspan. By default, cells are 1x1. The play button is pretty small, so that should be just fine for our layout. Notice also that the coordinates are (col=3, row=0). This will put the button in the upper right area of the screen. Again, feel free to change the coordinates to whatever you want (but be careful not to override an existing cell!)

▶️ Run the code! Verify that the button and video appear.

I ran it but it gave me an error and crashed!

Pro tip: Scroll down to the bottom of the stack trace in the console output. What looks to be 20 errors is actually just one error propagating to different places in the code. Most of it is noise, but the last few lines should tell you where in your code the error appeared. Look for a blue link that matches the name of your file. If you click it, Eclipse will take you to the offending line, which should help you narrow down the problem. Also, even the nastiest Exceptions have human-readable error messages that you can then google (expand the console until you can see the message).

Sometimes Java has trouble opening https links. Try changing the MEDIA_URL from the original code to http instead. If that still doesn't work, no big deal. I've found that opening URLs doesn't always work in JavaFX, but Files open just fine. Lucky for us, the ultimate goal is to display a video from a file, so skip to Step 3 if you can't get it to work with the sample URL.

Step 3

How can I put my own video in the MediaView?

Change the MEDIA_URL to point to your a URL on your local machine. To do this, make the following change, where "myInput.mp4" is the string specifying the name of your video:

-String MEDIA_URL = "https://liveexample.pearsoncmg.com/common/sample.mp4";
+String MEDIA_URL = new File("myInput.mp4").toURI().toString();

▶️ Run it and see your video play!

How do I tell the MediaView to use the output videos when I press the filter buttons?

You want to add an additional step at the end of your video-processing event handlers. After the encoder is finished, create a new MediaPlayer object from a new Media with a new MEDIA_URL (*phew* 😅) and tell the existing MediaView to use that new MediaPlayer instead of whatever it is currently using.

// Inside the button event handler...
encoder.finish(); // this should already be in the code

// Reassign the Media source
String MEDIA_URL_OUTPUT = new File("myOutput.mp4").toURI().toString();

Media outputMedia = new Media(MEDIA_URL_OUTPUT);
MediaPlayer outputPlayer = new MediaPlayer(outputMedia);
mediaView.setMediaPlayer(outputPlayer);

Notice how everything is new except the mediaView. By using the same mediaView, we don't have to re-add the mediaView to the root when we change mediaPlayers, and the view should update seamlessly whenever the video processing is finished!

Yay! Are we done yet?

Just one last change: the play button's event handler still references the original mediaPlayer. When you press play after running a filter, the input video will play instead of the output video. Unfortunately, we can't just reassign the old mediaPlayer because we're inside a lambda (the event -> {...} block). Variables outside a lambda are effectively final (we cannot change them, we can only call methods on them), so the following will cause an error:

// This change will NOT work because mediaPlayer was defined outside the lambda
Media outputMedia = new Media(MEDIA_URL_OUTPUT);
-MediaPlayer outputPlayer = new MediaPlayer(outputMedia);
+mediaPlayer = new MediaPlayer(outputPlayer);
mediaView.setMediaPlayer(outputPlayer);

So, how do we work around this? Fortunately, a MediaView always knows what MediaPlayer it is holding, so instead of trying to update the global mediaPlayer reference in each of our filters' event handlers, we can just make one change in our button's event handler to tell it to always use whatever MediaPlayer the mediaView is using:

playButton.setOnAction(e -> {
-    mediaPlayer.play();
+    mediaView.getMediaPlayer().play();
});

▶️ Run it! Choose a filter and watch the new video appear. Press "PLAY" and verify it plays.

Optional tweaks

My video is too big. Can I scale it down?

Yup! You can scale the MediaView to a particular dimension using its setFitWidth(double width) and setFitHeight(double height) methods. Just specify a size in the parameters of each (600x400 is decent, and has a 3:2 resolution so it should comfortable display any video without too much black space).

Instead of specifying the exact size, can I use the window's size?

Look into property binding. The idea is to bind the fitWidth and fitHeight properties to the scene's width and height properties. The video will dynamically scale when you resize the window. I'll leave this as an exercise. Good luck!

@MuhamedHabib
Copy link

thanks, just i have one other question, how can simply create a button that let me upload videos to my app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment