Class FfmpegLibrary
It never throws. isAvailable() is the whole interface for anything on a
decision path, and it reports false for every way this can go wrong: a build for
another operating system, a build for another processor, no build at all, a temporary directory
that cannot be written to, a library that loads but is missing an entry point. Each of those
leaves a sentence in failure() saying which one it was, because a decoder that is
silently absent is indistinguishable from one that is present and declining the file.
That matters more than it sounds. VideoDecoder.supports must never throw and must be
cheap, and it runs once per installed decoder every time anything at all is opened. If this
class threw on a machine with no native (which is every machine that has not built one), then
one absent library would make the whole probe unusable and the pure-Java decoders behind it
would stop being reachable.
Where it looks, in order
-Dlimn.video.ffmpeg.library=<directory>: a directory holding the libraries, loaded from where they lie. For a developer pointing at a build tree, and for an installer that has already laid the files out beside the application.java.library.path:System.loadLibrary, letting the operating system resolve the FFmpeg libraries the way it resolves any other dependency. For a package that installed them where the loader already looks.- The classpath: extracted from the jar to a cache directory and loaded from there.
This is the path a plain
java -jartakes, and the reason the extraction rules below exist.
Extraction
The cache directory is named after a digest of the libraries themselves, so two builds never collide, an upgraded application never loads yesterday's library, and a directory left behind by an older version is simply never consulted again. Files are written to a private temporary directory and the whole directory is then moved into place in one step, so a second process either sees nothing or sees everything, never a half-written library, which would load and then crash rather than fail.
Two applications extracting at once is therefore ordinary: both write their own temporary directory, one move wins, and the loser deletes its copy and uses the winner's. Neither waits for the other and there is no lock file to be left behind by a process that was killed.
None of that happens directly in the temporary directory, because that directory is
shared. On most Unix systems java.io.tmpdir is /tmp, which every local
account can write to, and the digest naming the extraction directory is taken over bytes that
ship inside the application, so any other account on the machine can work the path out offline
and create it first, and everything found there is handed to System.load. Extraction
therefore happens one level down, in a directory of this user's own that is created readable and
writable by nobody else; a directory already there that this user does not own, or that anyone
else can write into, is refused and reported through failure() rather than loaded from.
Where nothing can be written (a read-only temporary directory, a container with no writable
filesystem, a hardened deployment), extraction fails, isAvailable() is false and
failure() says so. -Dlimn.video.ffmpeg.cache=<directory> names somewhere else
to try, which is the answer for a deployment that has a writable directory but not that one.
Running on JDK 24 and later
Loading a native library became a restricted operation in JDK 24. It still works, and it
prints a four-line warning on every run naming this class; a future release is documented to
refuse it instead. An application that ships this decoder should therefore launch with
--enable-native-access=ALL-UNNAMED, or, once it has a module descriptor, with its own
module named instead of ALL-UNNAMED.
The flag cannot simply be added to a launcher that might also run on JDK 17: a JVM that does not know an option does not warn about it, it refuses to start. Add it where the launcher knows which JDK it is on: a jpackage image knows, and a start script can test.
Threading, and why the first call is the expensive one
Any thread. The load is attempted once per class loader and the outcome, success or failure,
is remembered: a machine with no native pays one failed lookup for the life of the process
rather than one per call to supports.
That one attempt is not cheap, and every path into video reaches it. On a build
carrying the libraries in its jar it reads the manifest resource, digests every library's bytes
to name the cache directory, copies each of them out of the jar (tens of megabytes), moves the
directory into place, links them in dependency order and runs the native identity probe. It
holds a global lock while doing so, so a second thread that asks meanwhile waits for all of it.
On the UI thread it is a freeze of that length, and the call that triggers it is as likely to be
a file chooser asking whether a clip is playable as an actual open. FfmpegVideoDecoder.warmUp() is what pays it somewhere else; every call after the first is a
lock and a field read.
-
Field Summary
Fields -
Method Summary
Modifier and TypeMethodDescriptionstatic Stringfailure()static booleanAny thread, and the first call in a process is a slow one: it is the call that extracts and links the libraries, which on a build carrying them is tens of megabytes of copying, and it does that under a global lock, so every other thread asking meanwhile blocks behind it too.static Stringplatform()
-
Field Details
-
LIBRARY_PROPERTY
A directory holding the libraries, to be loaded from where they lie rather than extracted.- See Also:
-
CACHE_PROPERTY
Where to extract to, when the default temporary directory cannot be written to.- See Also:
-
-
Method Details
-
isAvailable
public static boolean isAvailable()Any thread, and the first call in a process is a slow one: it is the call that extracts and links the libraries, which on a build carrying them is tens of megabytes of copying, and it does that under a global lock, so every other thread asking meanwhile blocks behind it too. Later calls are a lock and a field read. Do not let the first one happen on the UI thread;
FfmpegVideoDecoder.warmUp()exists to make it happen on a worker instead.- Returns:
- whether the native library is loaded and usable. Never throws, on any machine, for any reason; the first call attempts the load and every call afterwards reports what that one found.
-
failure
- Returns:
- why
isAvailable()is false, in one sentence naming the platform looked for and what went wrong, or null when the library did load
-
platform
- Returns:
macos-aarch64and the like: the directory this machine's build lives in
-