v0.2: All videos folder, shared watched state, release-order sorting, official icon, OAuth plumbing, docs
Some checks failed
release / build-release (push) Has been cancelled

- per-creator "All videos" folder (release order across sub-channels); sub-channel copies use
  cpost: ids and UserDataMirror keeps played/resume state in sync
- SortNameUpdater forces release-order sort names so every client lists newest first by default
- official Floatplane icon as channel image (Primary only — a channel Thumb hid video thumbnails
  in Continue Watching) and as 16:9 plugin thumbnail; one-time image replacement on upgrade
- OAuth device-flow login (FloatplaneAuthService + settings UI); needs a Floatplane client id;
  Turnstile verified domain-bound so password login inside Jellyfin is impossible
- generic install.sh (linuxserver / official image / native), thumb.png in package + manifest
- README rewritten for a general audience, doc/usage.md with screenshots, 60 tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 16:36:32 +01:00
parent 2a883c02de
commit bf476134c2
33 changed files with 1478 additions and 155 deletions

230
README.md
View File

@@ -1,116 +1,150 @@
# jellyfin-plugin-floatplane
# Floatplane for Jellyfin
A Jellyfin server plugin that exposes your [Floatplane](https://www.floatplane.com)
subscriptions (creators → channels → videos, plus live streams) inside
Jellyfin and all its clients, so Floatplane content shows up next to the
rest of the library on the Pi5 server and plays through the normal
Jellyfin players.
A [Jellyfin](https://jellyfin.org) server plugin that brings your
[Floatplane](https://www.floatplane.com) subscriptions into Jellyfin.
Creators, their sub-channels and every video appear as a **Floatplane**
channel under *My Media* in the web client and in all Jellyfin apps, and
play through the normal Jellyfin player — with watched state, resume,
"Continue Watching", "Recently Added" and search, like any other library.
**Status (2026-10-25):** browsing works (verified against a local Jellyfin
10.10.7); playback proxy is implemented but still needs a real session
cookie to be verified. See [`doc/plan.md`](doc/plan.md) for milestones and
[`doc/floatplane-api-notes.md`](doc/floatplane-api-notes.md) for the API.
![Home screen with the Floatplane tile, Continue Watching and Recently Added](doc/img/home.png)
The plugin is a pure managed .NET assembly (AnyCPU IL), so one build runs
on both `linux-x64` (dev box) and `linux-arm64` (the Pi5).
## Features
## Prerequisites
- **Creators → All videos → sub-channels.** Each creator you subscribe to
gets an *All videos* folder (every sub-channel in release order, newest
first) plus one folder per sub-channel (TechLinked, ShortCircuit, …).
- **Real metadata**: thumbnail, description, duration, release date,
sub-channel as *Genre*, creator as *Studio*.
- **Newest first by default** in every Jellyfin client, without touching
the sort menu.
- **Watched/resume state is shared** between the *All videos* copy and the
sub-channel copy of a video.
- **Low-CPU playback**: the server remuxes Floatplane's H.264/AAC stream
(stream copy, no re-encode). Quality levels show up as *Versions*.
- **Home screen integration**: "Recently Added in Floatplane" and
"Continue Watching" work out of the box.
- **Browse without logging in**: public creators (e.g. `linustechtips`)
can be listed by url-name; playback needs a logged-in session.
- **Search test tool** for Floatplane's own search API on the settings
page, and a scheduled task that refreshes the catalogue.
| Target | What to install |
|--------|-----------------|
| Arch / Garuda (dev box) | `sudo pacman -S dotnet-sdk-8.0` (Jellyfin 10.10.x = .NET 8) — for Jellyfin 12.x use `dotnet-sdk` (.NET 10) |
| Debian / Ubuntu | `apt install dotnet-sdk-8.0` from the Microsoft repo |
| Pi5 (runtime only) | nothing — the plugin runs inside the existing `linuxserver/jellyfin` container |
![Creator folder: All videos first, then the sub-channels](doc/img/creator-folder.png)
Optional, for local testing: Docker (to run a throw-away Jellyfin of the
same version as the Pi5 with the plugin folder mounted).
## Requirements
## Build
| | |
|---|---|
| Jellyfin | **10.10.x** (plugin targetAbi `10.10.0.0`, .NET 8). Jellyfin 12.x needs a separate build (not available yet). |
| Architecture | one AnyCPU DLL — runs unchanged on x64 and arm64 (Raspberry Pi 4/5 etc.). |
| Floatplane | an account with an active subscription for playback. |
## Install
**From the plugin repository (recommended)**
1. Dashboard → Plugins → **Repositories** → **+**. Name it anything and use
this URL:
```
https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/manifest.json
```
2. Dashboard → Plugins → **Catalog** → section *Channels* → **Floatplane**
→ **Install**.
3. Restart Jellyfin. The plugin shows as *Active* under *My Plugins*.
Updates then appear in the catalog like any other plugin.
**With the install script** (Docker — linuxserver.io or official image — or
a native install; detects the right plugin directory, verifies the checksum,
restarts Jellyfin):
```bash
curl -fsSL https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/install.sh | sh -s -- --dry-run
curl -fsSL https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/install.sh | sh
```
Environment overrides: `TAG` (`latest` or `vX.Y.Z`), `JELLYFIN_CONTAINER`
(default `jellyfin`, empty = native), `JELLYFIN_CONFIG_DIR`, `PLUGINS_DIR`,
`PUID`/`PGID`.
**Manually**: download `floatplane_<version>.zip` from the
[releases](https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases),
unzip it into `<jellyfin data>/plugins/Floatplane_<version>/` and restart.
## Log in
Floatplane's password login requires a Cloudflare captcha that only works on
floatplane.com, so the plugin cannot ask for your username and password.
Two options, both on **Dashboard → Plugins → Floatplane**:
1. **Session cookie** (works today): log in at floatplane.com in a browser,
copy the value of the `sails.sid` cookie (DevTools → Application/Storage
→ Cookies), paste it into the settings page, press **Validate cookie**
(shows your username and subscriptions) and **Save**.
2. **Login with Floatplane** (OAuth device flow — the way Floatplane's own
apps and clients like Hydravion or Wasserflug log in). This needs a
client id issued by Floatplane; without one the button reports
`invalid_client`.
Step-by-step instructions, every setting explained and troubleshooting:
**[doc/usage.md](doc/usage.md)**.
## How it works (short)
- The channel lists creators → *All videos* / sub-channels → posts from
Floatplane's public listing API (ids `creator:…`, `all:…`, `channel:…`,
`post:…`, `cpost:…`).
- On play, the plugin fetches the post details with your session and hands
Jellyfin signed URLs to its own `Floatplane/Stream/…` endpoints.
- The stream endpoint asks Floatplane for the stream info (cached 6 h,
throttled to the ~2/min the API allows), downloads the HLS playlist and
rewrites it: the AES-128 key is fetched through the plugin with your
session, the video segments go straight to Floatplane's CDN. Jellyfin's
ffmpeg then stream-copies the result to the client.
- A scheduled task refreshes the catalogue and bumps the channel cache key
so Jellyfin re-lists the folders.
## Development
```bash
git clone https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane.git
cd jellyfin-plugin-floatplane
scripts/package.sh # → build/Floatplane_<ver>/ (DLL + meta.json), build/floatplane_<ver>.zip (+ .md5)
dotnet test # 59 unit tests (models, HLS rewriter, API client, catalogue)
scripts/package.sh # → build/Floatplane_<ver>/ (DLL, meta.json, thumb.png), build/floatplane_<ver>.zip (+ .md5)
scripts/package.sh 1.2.3.0 https://example.com/dl # versioned package + build/manifest.json
```
The VS Code `build` task (`Ctrl+Shift+B`) runs the same script. Plain
`dotnet build` works too (`jellyfin-plugin-floatplane.sln`).
- Needs the .NET 8 SDK; the test host also needs the ASP.NET Core 8 shared
runtime (Arch: `sudo pacman -S dotnet-sdk-8.0 aspnet-runtime-8.0`).
- The VS Code `build` task (`Ctrl+Shift+B`) runs `scripts/package.sh`.
- Local end-to-end test against a throw-away Jellyfin of the right version:
## Install
```bash
mkdir -p .jellyfin-test/config/data/plugins
cp -r build/Floatplane_0.2.0.0 .jellyfin-test/config/data/plugins/ # writable copy
docker run --rm -p 8096:8096 -v "$PWD/.jellyfin-test/config:/config" \
lscr.io/linuxserver/jellyfin:10.10.7ubu2404-ls80
# logs: .jellyfin-test/config/log/log_*.log — plugin status: Dashboard → Plugins
```
**From the plugin repository (recommended):** Dashboard → Plugins →
Repositories → add
- Every push to `master` runs the tests and publishes the rolling `latest`
release (zip, md5, `manifest.json`, `install.sh`); a `vX.Y.Z` tag publishes
a versioned release.
```
https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/manifest.json
```
More: [`doc/plan.md`](doc/plan.md) (milestones, roadmap),
[`doc/floatplane-api-notes.md`](doc/floatplane-api-notes.md) (the API as
observed), [`doc/research/`](doc/research/) (Jellyfin plugin + Floatplane
API research and reference sources), and the
[project wiki](https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/wiki).
then Catalog → Channels → Floatplane → Install → restart Jellyfin.
## Credits & disclaimer
**Manual, on the Pi5 (linuxserver image):**
```bash
curl -fsSL https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/install-pi5.sh | sh -s -- --dry-run
# then without --dry-run; it installs into /srv/docker/jellyfin/data/plugins/ and restarts the container
```
**Manual, from a local build:**
```bash
# linuxserver image keeps data under /config/data (container path);
# host path on the Pi5: /srv/docker/jellyfin/data/plugins/ (infra-Doc services/media-stack.md)
cp -r build/Floatplane_0.1.0.0 /srv/docker/jellyfin/data/plugins/
docker restart jellyfin
```
> The Pi5 runs Jellyfin **10.10.7** but `lscr.io/linuxserver/jellyfin:latest`
> is already **12.3** (.NET 10). Pin the tag (`10.10.7ubu2404-ls80`) before
> any `docker pull`, or the plugin becomes `NotSupported` overnight.
Then Dashboard → Plugins → Floatplane → paste your `sails.sid` cookie
(the page explains where to find it) → Validate → Save. A "Floatplane"
channel appears under My Media: creators → sub-channels → videos.
Without a cookie you can still browse creators listed under
"Extra creators" (public data); playback needs the cookie.
## How it works (short)
- `FloatplaneChannel` (`IChannel`) lists creators → sub-channels → posts
from Floatplane's public listing API; ids are `creator:…`, `channel:…`,
`post:…`.
- On play, `IRequiresMediaInfoCallback` fetches the post (login) and hands
Jellyfin signed URLs to the plugin's own `Floatplane/Stream/…` endpoints.
- The stream endpoint calls `delivery/info` (cached, throttled to ~2/min),
fetches the HLS playlist, and rewrites it: the AES-128 key URI points
back at the plugin (which fetches the key with the cookie), segments go
straight to the CDN. ffmpeg then remuxes (stream copy) for the client.
- A scheduled task refreshes the catalogue and bumps the channel cache key.
## Test
```bash
dotnet test # unit tests (models, HLS rewriter, API client with fixtures from the HAR)
```
The test host needs the ASP.NET Core 8 shared runtime (the plugin references
Jellyfin's controllers): on Arch `sudo pacman -S aspnet-runtime-8.0`.
Local end-to-end test with a disposable Jellyfin matching the Pi5 version:
```bash
mkdir -p .jellyfin-test/config/data/plugins
cp -r build .jellyfin-test/config/data/plugins/Floatplane_0.1.0.0 # writable copy, not :ro
docker run --rm -p 8096:8096 -v $PWD/.jellyfin-test/config:/config \
lscr.io/linuxserver/jellyfin:10.10.7ubu2404-ls80
# logs: .jellyfin-test/config/log/log_*.log ; plugin status: Dashboard → Plugins
```
## Docs
- `doc/plan.md` — goal, stack, milestones, roadmap
- `doc/floatplane-api-notes.md` — endpoints, auth, HLS/AES-128 playback flow
- `doc/research/jellyfin-plugin-research.md` — plugin API research (10.10 vs 12.x, channels, proxy pattern, packaging, paths)
- `doc/research/floatplane-api-research.md` — community API docs, OAuth device flow, DPoP, rate limits, other clients
- `doc/research/reference-sources/` — snapshots of other clients' source for study (chaosflix is the template to follow)
- `doc/har/` — the captured browser session (**gitignored, contains credentials**)
- Gitea wiki (once the repo exists) — architecture and design decisions
Unofficial, not affiliated with or endorsed by Floatplane Media Inc.
The plugin uses Floatplane's unofficial API documented by the community
([jamamp/FloatplaneAPI](https://github.com/jamamp/FloatplaneAPI)) and
borrows the channel + signed-proxy pattern from
[chaosflix-jellyfin](https://github.com/praetorianer777/chaosflix-jellyfin).
Use it with your own subscription, for personal viewing. The Floatplane
name and logo belong to Floatplane Media Inc.