Java Module 18 – Desktop GUIs with JavaFX (and Swing for legacy code)
JavaFX animation, audio and video
Animate JavaFX nodes with transitions, Timeline and AnimationTimer, and play sound and video with Media, MediaPlayer, MediaView and AudioClip.
What you will learn
- Build node animations with transitions and Timeline key frames, and combine them in sequence or in parallel
- Run per-frame logic with AnimationTimer using its timestamp, not an assumed frame rate
- Use Media, MediaPlayer, MediaView and AudioClip to play audio and video, and handle unsupported files
Before you start
What you’ll learn · Before you start
What you will learn
- Build node animations with transitions and Timeline key frames, and combine them in sequence or in parallel
- Run per-frame logic with AnimationTimer using its timestamp, not an assumed frame rate
- Use Media, MediaPlayer, MediaView and AudioClip to play audio and video, and handle unsupported files
Before you start
On this page
JavaFX animates a node by changing one of its properties, such as translateX, opacity or rotate, a little on
every frame, and it plays audio and video through its javafx.media module. There are three ways to animate. A
transition is a ready-made animation of one node, such as a TranslateTransition that slides a node 100 pixels to
the right. A timeline moves any number of properties through key frames that you place on a time line. An
animation timer calls your own code once per frame. Every JavaFX animation runs on the JavaFX Application Thread,
and transitions and timelines follow the clock rather than counting frames. For sound and video, a Media object
names a file or a URL, a MediaPlayer plays it, a MediaView shows its video, and an AudioClip plays short sounds
such as clicks.
Three ways to animate a node
| Tool | You give it | JavaFX works out | A good fit |
|---|---|---|---|
A transition: TranslateTransition, FadeTransition, RotateTransition, ScaleTransition and others |
one node, a duration and the value to reach | every value in between | moving, fading, turning or growing one node |
Timeline |
key frames: at which moment which properties have which values, and what to run then | the values between the key frames | several properties at once, or actions at fixed times, such as a clock |
AnimationTimer |
a handle(long now) method |
nothing: it calls you once per frame with the time | games and simulations that compute every frame themselves |
Transitions and timelines are both Animation objects, so they share the controls: play(), pause(), stop(),
jumpTo(duration), a cycle count and a rate. An interpolator decides how fast the value moves at each moment:
LINEAR keeps one speed, while the default of every transition, EASE_BOTH, starts and ends slowly. A
SequentialTransition plays animations one after another, and a ParallelTransition plays them at the same time. A
PauseTransition waits for a set time, which spaces out the steps of a sequence. Setting the cycle count repeats an
animation, and autoReverse plays every second cycle backwards. A scene’s reduced-motion preference tells an
application to keep movement to a minimum, for people who feel unwell when the screen moves.
Easing and timing, measured
The examples run as single source files on JDK 25 with the JavaFX 27 SDK unpacked beside them, and the JDK reads
the JavaFX options from the argument file fx.args. The last two options draw without a screen; leave them out on
your computer to see the windows:
--module-path javafx-sdk-27/lib
--add-modules javafx.controls,javafx.media
--enable-native-access=javafx.graphics,javafx.media
-Dglass.platform=headless
-Dprism.order=sw The first program puts a ball on a 400 ms move of 100 pixels and stops the animation at every 100 ms with
jumpTo, for four interpolators. The helper FxRun starts JavaFX without an Application class; the launcher finds
it in FxRun.java beside the program:
Easing.java
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
/** Where a 400 ms, 100-pixel move puts a ball every 100 ms, for four interpolators. */
public class Easing {
public static void main(String[] args) throws Exception {
FxRun.start();
FxRun.onFx(() -> {
System.out.printf("%-20s%8d%8d%8d%8d%8d%n", "time (ms)", 0, 100, 200, 300, 400);
Interpolator[] curves = {null, Interpolator.LINEAR, Interpolator.EASE_IN, Interpolator.EASE_OUT};
for (Interpolator curve : curves) {
Circle ball = new Circle(10);
TranslateTransition move = new TranslateTransition(Duration.millis(400), ball);
move.setByX(100);
if (curve != null) {
move.setInterpolator(curve);
}
move.play();
move.pause(); // stopped here, jumpTo() moves the ball to any moment of the animation
StringBuilder row = new StringBuilder();
for (int ms = 0; ms <= 400; ms += 100) {
move.jumpTo(Duration.millis(ms));
row.append(String.format("%8.1f", ball.getTranslateX()));
}
String name = move.getInterpolator().toString().replace("Interpolator.", "");
System.out.printf("%-20s%s%n", curve == null ? name + " (default)" : name, row);
}
});
Platform.exit();
}
} FxRun.java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
/** Starts JavaFX without an Application subclass and runs code on the JavaFX Application Thread. */
final class FxRun {
private FxRun() {
}
/** Starts the JavaFX runtime and waits until its thread is running. */
static void start() throws InterruptedException {
CountDownLatch started = new CountDownLatch(1);
Platform.startup(started::countDown);
started.await();
}
/** Computes a value on the JavaFX Application Thread and returns it. */
static <T> T call(java.util.function.Supplier<T> task) throws InterruptedException {
Object[] result = new Object[1];
onFx(() -> result[0] = task.get());
@SuppressWarnings("unchecked")
T value = (T) result[0];
return value;
}
/** Runs the task on the JavaFX Application Thread and waits for it, at most 10 seconds. */
static void onFx(Runnable task) throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
Platform.runLater(() -> {
try {
task.run();
} finally {
done.countDown();
}
});
if (!done.await(10, TimeUnit.SECONDS)) {
throw new IllegalStateException("the JavaFX Application Thread did not answer");
}
}
} from session import javafx, run
javafx()
run("""
java @fx.args Easing.java
""") Output
$ java @fx.args Easing.java time (ms) 0 100 200 300 400 EASE_BOTH (default) 0.0 18.8 50.0 81.3 100.0 LINEAR 0.0 25.0 50.0 75.0 100.0 EASE_IN 0.0 16.7 44.4 72.2 100.0 EASE_OUT 0.0 27.8 55.6 83.3 100.0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 easing_session.py
All four curves start at 0 and end at 100 pixels on time; they differ in between. After a quarter of the time,
LINEAR has covered a quarter of the way, 25 pixels, while the default EASE_BOTH has covered only 18.8, because it
spends the first fifth of the time speeding up. EASE_IN lags all the way and EASE_OUT leads all the way. Since
JavaFX 26, Interpolator.ofSpline takes the same four numbers as a CSS cubic-bezier() curve, so a curve designed
for the web works here too.
The second program times a transition, a timeline and an animation timer on a shown stage with the system clock:
import java.util.concurrent.CountDownLatch;
import javafx.animation.AnimationTimer;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
/** A transition, a timeline and an animation timer on a shown stage, timed with the system clock. */
public class Timings {
static long elapsedMs(long since) {
return (System.nanoTime() - since) / 1_000_000;
}
public static void main(String[] args) throws Exception {
FxRun.start();
CountDownLatch finished = new CountDownLatch(3);
FxRun.onFx(() -> {
Circle ball = new Circle(20, 50, 10);
Stage stage = new Stage();
stage.setScene(new Scene(new Pane(ball), 300, 100));
stage.show();
// 1. A transition: 400 ms, 100 pixels to the right.
TranslateTransition move = new TranslateTransition(Duration.millis(400), ball);
move.setByX(100);
long moveStart = System.nanoTime();
move.setOnFinished(e -> {
System.out.printf("TranslateTransition, 400 ms: finished after %d ms at translateX %.1f%n", elapsedMs(moveStart), ball.getTranslateX());
finished.countDown();
});
move.play();
// 2. A timeline: key frames at 100, 200, 300, 400 and 500 ms, each counting a tick.
int[] ticks = {0};
Timeline clock = new Timeline();
for (int i = 1; i <= 5; i++) {
clock.getKeyFrames().add(new KeyFrame(Duration.millis(100 * i), e -> ticks[0]++));
}
long clockStart = System.nanoTime();
clock.setOnFinished(e -> {
System.out.printf("Timeline, 5 key frames: %d ticks, finished after %d ms%n", ticks[0], elapsedMs(clockStart));
finished.countDown();
});
clock.play();
// 3. An animation timer: called once per frame; counts the frames of its first 500 ms.
new AnimationTimer() {
long first = -1;
int frames;
@Override
public void handle(long now) {
if (first < 0) {
first = now;
}
frames++;
if (now - first >= 500_000_000L) {
stop();
System.out.printf("AnimationTimer: %d frames in %d ms%n", frames, (now - first) / 1_000_000);
finished.countDown();
}
}
}.start();
});
finished.await();
Platform.exit();
}
} from session import javafx, run
javafx()
run("""
java @fx.args Timings.java
""") Output
$ java @fx.args Timings.java TranslateTransition, 400 ms: finished after 412 ms at translateX 100.0 Timeline, 5 key frames: 5 ticks, finished after 503 ms AnimationTimer: 31 frames in 501 ms
This output changes from run to run: The times and the number of frames change by a few milliseconds and a frame or two from run to run.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 timings_session.py
The transition and the timeline finished within a few milliseconds of 400 and 500 ms: a JavaFX animation follows
the clock, and its last frame lands slightly after the moment it was waiting for. The animation timer saw about 30
frames in half a second, about 60 a second on this computer. A screen that refreshes 120 times a second, a busy
laptop or a hidden window gives another number, so per-frame code must use the now timestamp it is given, never an
assumed frame rate.
Sound and video in four classes
The four media classes and how sound and video flow between them
Text description of the diagram
The diagram runs from top to bottom.
1. "A file or URL" (file:, http:, https: or jar:) is the source. 2. "new Media(uri)" turns it into a Media object, which knows the duration, the size of the video and the tracks. 3. "new MediaPlayer(media)" creates the player, with play, pause, seek and volume. Its status goes from UNKNOWN to READY to PLAYING. 4. The player sends video frames to a MediaView, which shows them, and sound to the speakers.
A dashed path on the side shows the AudioClip: "new AudioClip(uri)" loads a short sound from the same kind of source and keeps it in memory, and play() sends it straight to the speakers.
A Media object is the source: its duration, the size of its video and its tracks. A MediaPlayer plays one
Media and has the controls: play, pause, stop, seek and volume. A MediaView is a node that shows the video of a
player; audio plays without one. An AudioClip loads a short sound into memory and plays it at once, as often as you
like, even several times at the same moment. A player works asynchronously: right after new MediaPlayer(...) its
status is UNKNOWN and the duration is unknown too, so code that needs them waits for onReady. When a player is no
longer needed, dispose() frees what it holds.
JavaFX 27 plays WAV and AIFF files with uncompressed audio, MP3 files, and MP4 files, also named .m4a or .m4v,
with H.264 or H.265 video and AAC audio, plus HTTP Live Streaming, from file:, http:, https: and jar: URLs.
Audio in other formats, such as Ogg, FLAC or WebM, has to be converted first. A file in a format that JavaFX does
not support makes new Media(...) throw a MediaException whose type, such as MEDIA_UNSUPPORTED, says why.
India-specific: GTU Object Oriented Programming-I
GTU’s syllabus for subject 3140705 lists animation, and video and audio, in its two JavaFX units; this lesson covers both on the current JavaFX release. MySmartCoPilot is not affiliated with GTU, and the official syllabus decides what is examined.
Check yourself
4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Composing and timing animations, measured Pro: opens with a Pro, Premium or Ultimate pass
Real screens chain animations, repeat them, and have to keep their speed on a slow computer. This part measures composed animations, reproduces the frame-rate bug with numbers, handles reduced motion and ends with an exercise.
In this part:
- Sequences, parallels, cycles and rates, measured
- Moving by the clock, with a slow frame reproduced
- Reduced motion: what JavaFX 27 does, and what it leaves to you
- The same curve on JavaFX 25 and JavaFX 27
- Your turn: a traffic light that pauses
11 runnable examples (CheckTraffic.java, Java, Python, starter/TrafficLight.java) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Playing sound and video reliably, with failures reproduced Pro: opens with a Pro, Premium or Ultimate pass
Media code fails in ways that throw nothing: a player that never becomes ready, a sound that stops halfway, a seek bar that fights the user. This part reproduces each failure, measures what it costs and shows the fix.
In this part:
- A MediaPlayer’s life, event by event
- The player that stopped: garbage collection
- Files that will not play
- Sound effects: one AudioClip, not a player per click
- A seek bar that does not fight the user
- Video: a MediaView only shows what the player plays
12 runnable examples (Java, Player.java, Python, Wav.java) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid · Warm-up
When do you use a transition, a Timeline or an AnimationTimer in JavaFX?
A transition animates one property of one node towards a value you know in advance, such as sliding a card with TranslateTransition or fading a message with FadeTransition, and JavaFX computes every value in between with an interpolator. A Timeline suits several properties at once or actions at fixed moments: its key frames say which values to reach at which time and which handler to run then, as in a clock or a traffic light. An AnimationTimer calls handle(now) once per frame and computes nothing for you, which suits games and simulations whose next state depends on the current one. Transitions and timelines are Animation objects with play, pause, stop, a rate and a cycle count; a timer can only start and stop. All three run on the JavaFX Application Thread.
-
Mid to senior
A JavaFX game moves the player 4 pixels in every AnimationTimer call. One tester says it crawls when many enemies are on screen; another, with a 120 Hz monitor, says it runs too fast. Explain both reports, fix the movement and say how you would test the fix.
-
Mid to senior
A quiz app creates a new MediaPlayer for a buzzer sound on every wrong answer. In long sessions some buzzers never sound, and the thread count and memory of the process keep rising. Explain what is happening and redesign the sound code.
-
Mid to senior
A lecture player lets students open any audio or video file they have. Some files never start and show no message; others show a stack trace. Design how the player opens a file, explains problems in plain words and never hangs.
Key takeaways
- A transition animates one node, a
Timelineworks through key frames, and anAnimationTimercalls your code every frame. - Transitions and timelines follow the clock; a transition’s default interpolator,
EASE_BOTH, starts and ends slowly. - Per-frame code scales its steps by the
nowtimestamp, never by an assumed frame rate. Mediais the source,MediaPlayerplays it,MediaViewshows its video, andAudioClipplays short sounds.- A player is asynchronous: wait for
onReady.
References
- Package javafx.animation (JavaFX 27) (OpenJFX)
- Timeline (JavaFX 27) (OpenJFX)
- AnimationTimer (JavaFX 27) (OpenJFX)
- Interpolator (JavaFX 27) (OpenJFX)
- Package javafx.scene.media: supported media types and protocols (JavaFX 27) (OpenJFX)
- MediaPlayer (JavaFX 27) (OpenJFX)
- AudioClip (JavaFX 27) (OpenJFX)
- MediaView (JavaFX 27) (OpenJFX)
- Scene.Preferences (JavaFX 27) (OpenJFX)
- Release Notes for JavaFX 27 (OpenJDK)
- Release Notes for JavaFX 26 (OpenJDK)
- JEP 458: Launch Multi-File Source-Code Programs (OpenJDK)
- The java Command: command-line argument files (Java SE 25) (Oracle)
- GTU syllabus 3140705, Object Oriented Programming-I (Gujarat Technological University)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress