jellyfin, extracted from the platform as a plugin

Eighth plugin out, and the second resurrection: like soulseek, hono.ts carried
the router import and its mount commented out since 2026-08-13, with the
permission reduced to api: [], so the server booted clean while every panel
request 404'd. jellyfin_servers was also absent from the drizzle barrel, so
bun db:push would have dropped it.

Easier than soulseek in one specific way: mountPrefix derives /jellyfin from
the appName, which is exactly what the router already hardcoded, so no frontend
call site needed repointing. Soulseek needed 47 because its appName and its old
prefix differed. The frontend also already routed through one helper, so there
were five call sites rather than forty-seven.

Self-contained, unlike transmission and soulseek: jellyfin_servers is a
registry this plugin owns outright — label, URL, encrypted token, one active —
because more than one Jellyfin is a thing someone has, and more than one
Transmission is not.

Carries its own .prettierignore for the vendored hls.mjs, rather than leaving a
stale rule behind in the platform naming a path that no longer exists.
This commit is contained in:
officer
2026-08-19 03:17:32 +00:00
commit a7f1a71785
34 changed files with 41581 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
# Vendored hls.js
`hls.mjs` is **hls.js 1.6.16**, checked in rather than installed.
## Why it is here and not in `package.json`
Chromium cannot play an HLS playlist natively, so seeking inside a live Jellyfin transcode needs a player
library. `bunfig.toml` freezes installs (`frozenLockfile = true`) specifically so that adding a package is a
deliberate act with a reviewed lockfile diff — see `platform/CLAUDE.md` → "Installs are frozen", and the
2026-08-04 npm cache-package worm that prompted it.
Vendoring makes the trade explicit instead of implicit:
- **No install-time code.** A committed file has no `preinstall`/`postinstall` hook, which is the exact vector
that compromise used. hls.js's own tarball has no install script either — but a future version of it, or of
anything it might later depend on, could.
- **No transitive tree.** hls.js 1.6.16 publishes `dependencies: {}`, `peerDependencies: {}` and
`optionalDependencies: {}`, so nothing is being skipped by not resolving it. Its *build-time* devDependencies
do reach the affected packages (eslint 8 → file-entry-cache → flat-cache → keyv), but this artifact was
published 2026-04-13, months before those versions existed.
- **It is reviewable.** One unminified file in the diff. That is the point; do not replace it with a minified
build to save bytes.
The upstream `.d.ts` is *not* vendored — it imports a tree of internal type modules, which would undo the one
file rule. `hls.d.mts` next to it declares the API this player actually uses, by hand.
## Provenance
| | |
|---|---|
| Package | `hls.js@1.6.16` |
| Published | 2026-04-13 |
| Tarball | `https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz` |
| Tarball sha512 | `VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==` (matches the registry's published `dist.integrity`) |
| Extracted from | `package/dist/hls.mjs` |
| Local change | the trailing `//# sourceMappingURL=hls.mjs.map` line removed — the map is not vendored, and leaving the reference makes devtools 404 |
| `hls.mjs` sha256 | `45a4bb55e5346bf9284fa7e46b6fac7d62dce5b90394dcba9747d471940fea6e` as published; `ca442f1f963ff66607a242cc7be227b243770d86bfc3251bf675f8a342dfe798` as committed here (the two differ only by that removed line) |
| Licence | Apache-2.0 — `hls.LICENSE` |
## Updating it
```sh
V=1.6.17
curl -sO https://registry.npmjs.org/hls.js/-/hls.js-$V.tgz
# compare the sha512 against `curl -s https://registry.npmjs.org/hls.js/$V | jq -r .dist.integrity`
tar xzf hls.js-$V.tgz package/dist/hls.mjs package/LICENSE
```
Copy `package/dist/hls.mjs` over this one, drop the `sourceMappingURL` line, update the table above, and read
the diff. A version bump that arrives with a diff you did not read is the thing this directory exists to
prevent.
+28
View File
@@ -0,0 +1,28 @@
Copyright (c) 2017 Dailymotion (http://www.dailymotion.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
src/remux/mp4-generator.js and src/demux/exp-golomb.ts implementation in this project
are derived from the HLS library for video.js (https://github.com/videojs/videojs-contrib-hls)
That work is also covered by the Apache 2 License, following copyright:
Copyright (c) 2013-2015 Brightcove
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+63
View File
@@ -0,0 +1,63 @@
// Hand-written declarations for the vendored `hls.mjs`.
//
// Deliberately narrow: it declares the API this player actually calls, not hls.js's full surface. The upstream
// `.d.ts` is not vendored because it pulls in a tree of internal type modules, and the point of vendoring was
// to have ONE reviewable file. Anything added to the player that hls.js supports but this file does not gets
// declared here, in the open, rather than cast away at the call site.
export type HlsErrorData = {
fatal: boolean;
type: string;
details: string;
reason?: string;
error?: Error;
};
export type HlsLevel = { height?: number; width?: number; bitrate?: number };
export type HlsConfig = {
/** Where playback begins, in seconds. -1 means "the start", which is not the same as 0 for a live edge. */
startPosition?: number;
/** Called before `xhr.open`, so it may open the request itself and then set headers on it. */
xhrSetup?: (xhr: XMLHttpRequest, url: string) => void;
enableWorker?: boolean;
lowLatencyMode?: boolean;
maxBufferLength?: number;
maxMaxBufferLength?: number;
backBufferLength?: number;
};
declare class Hls {
constructor(config?: HlsConfig);
static isSupported(): boolean;
static readonly Events: {
MANIFEST_PARSED: string;
LEVEL_LOADED: string;
ERROR: string;
MEDIA_ATTACHED: string;
};
static readonly ErrorTypes: {
NETWORK_ERROR: string;
MEDIA_ERROR: string;
KEY_SYSTEM_ERROR: string;
MUX_ERROR: string;
OTHER_ERROR: string;
};
loadSource(url: string): void;
attachMedia(media: HTMLMediaElement): void;
detachMedia(): void;
startLoad(startPosition?: number): void;
stopLoad(): void;
recoverMediaError(): void;
destroy(): void;
on(event: string, listener: (event: string, data: HlsErrorData) => void): void;
off(event: string, listener?: (event: string, data: HlsErrorData) => void): void;
readonly levels: HlsLevel[];
currentLevel: number;
}
export default Hls;
+36705
View File
File diff suppressed because it is too large Load Diff