Skip to content

About

A Jellyfin plugin that adds a WebGPU based player to the web client.

Resources

Stars

52 stars

Watchers

3 watching

Forks

Latest commit

 

History

161 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jellyfin WebGPU Player
A WebGPU-based player plugin for Jellyfin Web

WebGPU Player

About

This plugin is in alpha!!!

This plugin adds a custom WebGPU-based media player as an optional player backend to Jellyfin in the browser, thus allowing (theoretically) any media to be played directly, which would otherwise need to be transcoded on the server. Notably, this includes HDR->SDR tone-mapping and multichannel audio downmixing.

The underlying WebGPU player also supports dynamically available codec modules, which enable GPU-accelerated decoding on otherwise unsupported platforms.

Similar to codec modules, the player supports dynamic post-processing modules i.e. "shaders". Users coming from MPV should be familiar with this concept. This enables support for advanced upscaling alogrithms such as Anime4K, sharpening filters, frame-blend motion smoothing, and more.

The player repository is located here: WebGPU Player

Currently supported formats

Installation and Requirements

WebGPU requires secure context (https) in order to run (web app accessing GPU hardware = serious). If you aren't connecting to Jellyfin in a secure context already, you should be. I can highly recommend a reverse proxy setup using Caddy.

Add this plugin as you would any other plugin: add this manifest to your plugin repositories, then install the "WebGPU Player" plugin from it:

https://raw.githubusercontent.com/alchemyyy/jellyfin-plugin-webgpu-player/refs/heads/main/manifest.json

Thats it! you're all set.

Additional Install Info

The File Transformation plugin is optional. When it is installed, the plugin rewrites Jellyfin Web through it, alongside other plugins that use it. Without it, the plugin's own middleware performs the same rewrites.

The plugin itself is basically a wrapper for injecting modifications into the Jellyfin Web client. The web client modifications live in jellyfin-webgpu-client. The player backend itself is its own project here: WebGPU-Player. This plugin also ships with my own flavor of hls.js for now.

WARNING: This plugin essentially overwrites the stock player, even though it allows access to the old backend. This means there most likely will be compatibility issues between this and other things that modify the player UX. Feel free to report any of these issues and I'll look into fixing them.

Unstable Builds

Unstable builds are pre-releases cut from main between releases, numbered after the release they follow (2.0.1.3 is the third after 2.0.1.0). To get them, add this repository alongside the one above:

https://raw.githubusercontent.com/alchemyyy/jellyfin-plugin-webgpu-player/refs/heads/unstable/manifest.json

Jellyfin then updates to each unstable build and each release. To leave, remove the repository; the server keeps its build until the next release. To roll back, uninstall the plugin, install an older version from its page, and restart; disable the Update Plugins scheduled task to stay there.

Development

This is meant to be a replacement solution to the Jellyfin desktop client "ecosystem". My philosophy here is that its a lot easier to develop a backend player, which is almost purely mechanically testable, than it is to modify or outright create a UX and stitch it into an existing native player program, with all the extra overhead of shipping and maintaining a standalone app a user base has to manually keep up to date.

Documentation

In an effort to be able to continually tackle issues in this project and iterate on it, I'm going back and forth with Claude on a comprehensive set of docs for the plugin and underlying player. These are still a huge WIP since I have to go through and manually clean everything up, but today they're still very useful for crash-coursing an AI agent through the project.

The plugin's docs are an mdBook in docs/, covering the client add-on and its Jellyfin integration. The engine has its own book in jellyfin-webgpu-client/vendor/webgpu-player/docs/.

Contributing and AI Usage

This plugin is practically vibe-coded, so feel free to not use it on that basis. Given this, however, I welcome any and all issues and contributions that are at least 0.1% human-contributed.

Issues

I will automatically close any issue that appears to be completely AI-driven and/or auto-submitted. Using AI to help convey the bulk content though is completely fine.

PRs

I will automatically do whatever I want with any PR that appears completely AI-driven. I expect to see human-generated reasoning in PR bodies. This applies especially to feature PRs.

I welcome anyone who wants to have their LLM chew on additional codec or device support.

Please oh please have your LLM generate and run tests on what it produces. This isn't a perfect solution, but tests are free and I don't mind letting them pile up since it'll all get cycled around and cleaned up from LLM to LLM anyway.

Dev Environment Requirements

  • bash (Git Bash on Windows) and git.
  • The .NET 10 SDK, for the server plugin.
  • Node.js 24 or later and npm 11 or later, for the client add-on and the engine.
  • For the engine's WebAssembly modules, which the first build of a checkout compiles: GNU Make, Emscripten 4.0.13, and rustup. Emscripten comes from the emsdk that EMSDK names, which emsdk_env sets; without EMSDK, ./build.sh installs the pinned version into the engine's own emsdk when its Emscripten check fails (it needs Python 3 and about 2 GB), and the engine's Makefiles use that emsdk from then on. The engine pins Rust 1.96.1 with the wasm32-unknown-unknown target.
  • Network access to GitHub on a build that lacks a pinned decoder package or pass file of the engine, whose releases ./build.sh installs.

Only for specific tasks:

  • uv (or Python 3.10 or later with ruamel.yaml), to set the version and package the plugin.
  • Python 3.10 or later and the pinned FFmpeg and FFprobe build (2026-03-01-git-862338fe31-full_build-www.gyan.dev), to generate or check the engine's codec vectors.
  • MKVToolNix, for the engine's Dolby Vision smoke media scripts.
  • mdBook 0.5.4, to build the plugin's and the engine's documentation.
  • CMake, Perl, GNU patch, and on Windows long path support, to build the engine's decoder packages from its wgpud/ submodule instead of installing their pinned release; see the engine's Set up a checkout.
  • Chrome, Edge, or Firefox with WebGPU and WebCodecs, over HTTPS or localhost, and a Jellyfin server, 12.1 or later, to run it. Firefox on Windows decodes HEVC in software only, which is below real time at 4K. The File Transformation plugin is optional.

Building from Source

git clone https://github.com/alchemyyy/jellyfin-plugin-webgpu-player.git
cd jellyfin-plugin-webgpu-player
./build.sh

build.sh initializes the submodules, builds the client add-on, embeds it, and builds the plugin. ./build.sh --help lists its options.

  • Jellyfin Web is a shallow submodule pinned to the commit the add-on builds against, and is only read. --jellyfin-web-path builds against another checkout.
  • The first build of a checkout also compiles the engine's WebAssembly modules, in the background while the add-on's npm install and the hls.js build run. Every build installs the engine's pinned decoder package and pass file releases, downloading only the pinned files that are missing. The plugin carries every package and pass file and offers them to clients; its dashboard settings page manages them.
  • --publish runs dotnet publish instead of dotnet build, for packaging.
  • The add-on's own checks run from jellyfin-webgpu-client/: npm run typecheck, npm run lint, npm test, and npm run build.

Test Install

./build.sh --publish
uv run .github/scripts/release.py package
python -m http.server --directory bin/package

Add http://localhost:8000/manifest.json as a plugin repository, then install the plugin from it.

  • For a server on another machine, package with --source-url-base http://<host>:8000 and use that host.
  • The server rejects a zip whose MD5 differs from the manifest's checksum, so package again after every build.

Live Development

./serve_jellyfin_web_client.sh --server http://<host>:8096

Open http://127.0.0.1:5173/web/ and sign in to your server. Jellyfin Web runs this checkout's add-on and plugin against your server's library, and both build again on every change.

  • Run it from the folder to work in, after one ./build.sh. The first run builds Jellyfin Web in the submodule jellyfin-webgpu-client/vendor/jellyfin-web/, once per pinned commit, into its gitignored node_modules/ and dist/, and stores the options in serve_jellyfin_web_client.json, so later runs need none.
  • Jellyfin Web, the add-on, and the plugin are served locally, and every other request goes to the server, so the browser sees one origin. A read the server redirects to another origin, such as a split tunnel's media host, is followed by the script, so the browser stays on that origin.
  • The plugin runs in a host of its own, Jellyfin.Plugin.WebGPUPlayer.DevHost, which signs requests in through your server and keeps its data in webgpu-player-plugin/data/. The server needs no plugin; an installed one, and its data, stay as they are.
  • Open pages reload after each successful add-on or plugin build and each edit of the plugin's settings page, and a failed add-on build shows its errors in place of Jellyfin Web. "liveReload": false in serve_jellyfin_web_client.json turns the reload off.

Project Map

Path Purpose
Jellyfin.Plugin.WebGPUPlayer/ Server plugin: project, entry point and service registration
Jellyfin.Plugin.WebGPUPlayer/Transformations/ The index.html and config.json rewrites: File Transformation registration and callbacks, the choice of rewriter, and the fallback middleware
Jellyfin.Plugin.WebGPUPlayer/Addon/ Embedded add-on catalog and the asset route
Jellyfin.Plugin.WebGPUPlayer/Modules/ The decoder packages and pass files the server offers: the store of bundled files, uploads, and disabled keys in the plugin's data folder, the readers of package and pass headers, and their routes
Jellyfin.Plugin.WebGPUPlayer/ClientDefaults/ The administrator's defaults for every client setting: the store of client-defaults.json in the plugin's data folder, its shape check, and its routes
Jellyfin.Plugin.WebGPUPlayer/Diagnostics/ The routes clients report crashed capability probes and playback fallbacks to, which check each report and write a log line per entry to the server log
Jellyfin.Plugin.WebGPUPlayer/Configuration/ The plugin's dashboard settings page and its controller, which mount the add-on's client defaults editor and manage the server's decoders and passes
Jellyfin.Plugin.WebGPUPlayer.DevHost/ A host of the plugin's routes for serve_jellyfin_web_client.sh, which signs requests in through a Jellyfin server of your own
bin/ Build output, one folder per project (generated, gitignored): the .NET projects, and the client add-on in bin/jellyfin-webgpu-client/
jellyfin-webgpu-client/ Client add-on: a private npm package with the add-on sources in src/ and their webpack, Vitest, ESLint and stylelint configuration
jellyfin-webgpu-client/src/strings/ The add-on's text: en-us.json is the source, and each translation is a <locale>.json beside it, written by Weblate
jellyfin-webgpu-client.tests/ The add-on's Vitest suites, mirroring jellyfin-webgpu-client/src/. They import the add-on through addons/webGPUPlayer/... and have their own ESLint config, built on the client's
jellyfin-webgpu-client/vendor/webgpu-player/ Submodule: the engine (alchemyyy/WebGPU-Player), an npm workspace of jellyfin-webgpu-client/
jellyfin-webgpu-client/vendor/webgpu-player-hls/ Submodule: the hls.js fork (alchemyyy/hls.js, branch fix/cals2)
jellyfin-webgpu-client/vendor/jellyfin-web/ Shallow submodule: the Jellyfin Web commit the add-on builds against, read-only
Jellyfin.Plugin.WebGPUPlayer.Tests/ xUnit tests
docs/ The plugin's documentation, an mdBook; docs/book/ is the built book
images/ The plugin banner (the source SVG, and the PNG that the package carries and the repository manifest's imageUrl names on main), and the plugin logo, the source of the book's favicon
build.sh Builds the add-on, embeds it, and builds or publishes the plugin
serve_jellyfin_web_client.sh Serves this checkout's add-on and plugin, built again on every change, in a local Jellyfin Web against a Jellyfin server of your own
build.yaml Plugin metadata: name, GUID, version and its changelog, targetAbi, and the artifacts to package
.github/scripts/release.py Sets the version, numbers unstable builds, packages the published plugin with a meta.json into a plugin repository manifest, records a packaged zip in another manifest, and writes the GitHub release notes
manifest.json Jellyfin plugin repository manifest of the releases, updated on main by the publish manifest workflow when a release's draft is published. The unstable branch holds only the manifest of the unstable builds, which that workflow updates when an unstable build's draft is published
.github/workflows/release.yml Release workflow, run by hand with the version to release, which ends in .0; its jobs validate it, build and package the plugin, write the release notes, tag the release commit with manifest.json, and draft the GitHub release, which goes out when the draft is published
.github/workflows/release-unstable.yml Unstable release workflow, run by hand on main; its jobs number the build after the latest release, build and package it, write the release notes, and draft it as a GitHub pre-release tagged v<version>-unstable, which goes out when the draft is published
.github/workflows/publish-manifest.yml Publish manifest workflow, run when a release's draft is published; it puts a release's commit with manifest.json on main, or records an unstable build in manifest.json on the unstable branch
.github/workflows/build-plugin.yml The build both release workflows call: it sets the version, builds and packages the plugin, and uploads the zip

Localization

The add-on's text is translated the way Jellyfin Web's is: one JSON file per language with a key for every string, maintained through Weblate.

  • jellyfin-webgpu-client/src/strings/en-us.json is the source and the fallback. Each translation is a <locale>.json beside it.
  • The add-on loads the translation for Jellyfin Web's display language, else the one for its base language (pt for pt-br). A string that is missing or empty there shows its en-us.json text.
  • Keys start with WebGPU. Words Jellyfin Web already translates, such as Default, Auto, Unknown and ButtonClose, use Jellyfin Web's own keys and translations.
  • {0}, {1} are placeholders, as in Jellyfin Web. A translation keeps them and may reorder them. WebGPUJoinedSentences ({0} {1}) joins two complete sentences, so a language can change the separator.
  • Names of technologies, standards and algorithms are not translated: WebGPU, HTML, WebCodecs, ACES, Reinhard, ST 2094-40, AC-4, RFC 7845 and Dave750.
  • jellyfin-webgpu-client.tests/strings/strings.test.ts checks the folder: the file names, that a translation has only source keys and keeps their placeholders, that every key the add-on translates exists, and that no source key is unused.

The Weblate component:

Setting Value
File format JSON file
File mask jellyfin-webgpu-client/src/strings/*.json
Monolingual base language file jellyfin-webgpu-client/src/strings/en-us.json
Language code style BCP style using hyphen as a separator, lower cased
Source language English

Nothing else needs translating. The server plugin's settings page is English only, as Jellyfin's plugin pages are, and Jellyfin does not translate plugin names or descriptions. Its client defaults editor is the exception: the add-on renders it, in the user's language. The engine renders no text either: the add-on translates the codes it reports.

About

A Jellyfin plugin that adds a WebGPU based player to the web client.

Resources

Stars

52 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages