# 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](https://plugins.jetbrains.com/plugin/31014). 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. JCEF[^1] 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 support[^2], 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.

Jetplay's media byte path, in two deployment modes (repo https://github.com/twangodev/jetplay):

**Local** (desktop IDE, JCEF runs in-process on the same machine as the file):
media file → FileByteSource → loopback server on 127.0.0.1 (CORS + HTTP Range + 206 Partial Content) → JCEF <video>.
The loopback server exists because the null-origin JCEF page cannot fetch file://, and file:// has no Range support, so the player cannot seek.

**Remote Dev** (JetBrains Client + remote host; JCEF runs client-side, the file lives on the host):
media file (host) → MediaAccessor @Rpc, readRange / streamFileBytes / Flow<ByteArray> → [RPC boundary] → loopback server (client) → JCEF <video> (client).
The client runs its own loopback server; every byte range it serves is fetched 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 V2[^5] 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 `@Rpc`[^6] 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 FFmpeg[^7] 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](https://github.com/twangodev/jetplay), and it keeps growing. To install, search for "jetplay" under Settings → Plugins → Marketplace.

## Notes

[^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`.
