Jetplay: Audio and Video Playback for JetBrains IDEs

So apparently JetBrains IDEs have never supported audio or video playback.

So when you open a video in IntelliJ, PyCharm, WebStorm, etc. all you get is to set up a file association so some other program can open it. And over a remote session there’s nothing useful to hand it to anyway, since the file lives on the host. So why not build a plugin to handle this all within the IDE itself? Here’s Jetplay. Now you can have audio and video playback within your JetBrains IDE.

The first version took an afternoon. Getting it to really work took quite a bit longer, and then Remote Dev came along and broke it all over again.

Just Use a Browser

JetBrains already ships a browser. JCEF1 is embedded Chromium, and Chromium plays audio and video. The first version did the obvious thing: load a small player page into JCEF and point an <audio> or <video> tag at the file.

And locally, that’s the whole trick: double-click and it plays. JCEF is happy to play a file:// URL, audio or video. The early versions of Jetplay were exactly that, a player with a progress bar and nothing else, and they worked fine.

The loopback server came later, when I added a waveform you could scrub. A null-origin page can play a file:// URL but can’t fetch() and decode its bytes, and drawing a waveform needs exactly that. file:// also has no real Range support2 , so seeking was shaky on big files too. So local media moved onto a tiny HTTP server on 127.0.0.1 that hands the page a real http:// URL, with the CORS header it wants, Accept-Ranges: bytes, and a 206 Partial Content for whatever range it asks for.3 Same playback, just a fetchable URL behind it.

Remote Development

All of that worked well, but only on local setups, which, for me, recently has been pretty rare. The first time I opened something over Remote Dev,4 it came apart. Video wouldn’t play at all. Audio would start, run for a couple of seconds, and then randomly cut out.

Remote Dev splits it across two machines: the file on the host, the browser on the client (since JCEF only runs there). And a loopback server only answers on its own machine. Put it on the host and the browser can’t reach it; put it on the client and there’s no file to read. Either way the player runs out of bytes, which is the cut-out, and why video never even started.

media file
audio or video on disk
loopback server
127.0.0.1:PORT/$token · CORS · Accept-Ranges · 206
JCEF <video>
in-process Chromium · seeks like the web
How a byte range gets from the file to the <video> tag. Local: a loopback server hands seekable bytes to the in-process browser. Remote Dev: the browser's on the client and the file's on the host, so the client's loopback server pulls each range from the host over @Rpc.

So I split the plugin in two and let the halves talk over the wire. There are four Plugin Model V25 modules: shared holds the contract, frontend is the player and the loopback server, client renders it in split mode, and backend runs FFmpeg on the host. The client runs its own loopback server, and every byte it needs it pulls from the host over an @Rpc6 call. When the browser seeks, the server turns that into readRange(offset, length), the host reads the slice off disk and sends the bytes back. Transcoding runs on the host too, where FFmpeg and the file already are.

You don’t see any of this. You double-click and it plays, whether the file is on your own disk or on a host three time zones away.

What it plays

Pretty much anything. If the embedded Chromium can decode it, it plays the moment you open it. If it can’t, a bundled FFmpeg7 transcodes it to WebM the first time, with a progress bar. Either way you just double-click.

The current list of formats lives on GitHub, and it keeps growing. To install, search for “jetplay” under SettingsPluginsMarketplace.

Footnotes

  1. Java Chromium Embedded Framework, a full Chromium embedded in the IDE. It already runs the Markdown preview and the login screens.
  2. The Range: bytes=… header. It asks for a slice of a file instead of the whole thing. Without it, seeking gets unreliable, because the player can’t ask for the bytes from 2:00 onward.
  3. Loopback only, a random token per file, and a Host-header check against DNS rebinding. It serves the files you’ve opened and nothing else.
  4. Gateway and the JetBrains Client: the IDE backend runs on a remote host, a thin client paints the UI on your laptop. Your files live on the host. You never see them locally.
  5. IntelliJ’s content-module system. A plugin splits into modules that load only where their target exists: host, client, or both.
  6. IntelliJ’s typed RPC over the Remote Dev protocol: an annotated Kotlin interface whose calls marshal across the host/client boundary, suspend functions and streams included.
  7. JavaCV’s bundled build, so there’s no system install and nothing on your PATH.