- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped) - IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the AES-128 key is fetched server-side with the session cookie, segments stay on the CDN - delivery/info cached per token lifetime and throttled (~2/min) - config page: paste sails.sid + validate, extra creators, quality, search test - scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow - research docs, HAR analysis (HAR itself gitignored), install-pi5.sh Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
9.1 KiB
jellyfin-plugin-floatplane — plan (2026-10-25)
Decisions taken 2026-10-25: Gitea repo
brasse/jellyfin-plugin-floatplane(private), build for Jellyfin 10.10.x (Pi5 stays on 10.10.7 for now), cookie session first (OAuth client id to be requested from Floatplane later), VOD first, and map/test the search API. Remaining open points are listed at the bottom.
Goal
Make Floatplane subscriptions browsable and playable inside Jellyfin on the
Pi5 (film.brasse-pc.eu) with no changes to any client: creators and
their sub-channels appear as a "Floatplane" channel under My Media in the
web client, Android/TV apps and Jellyfin Media Player; videos play through
the normal Jellyfin player with watched-state, resume and SyncPlay.
What the research established
Full reports: research/floatplane-api-research.md,
research/jellyfin-plugin-research.md,
HAR findings in floatplane-api-notes.md.
Jellyfin side
- Pi5 runs 10.10.7 (image pulled 2025-08-12).
linuxserver/jellyfin:latestis now 12.3 (.NET 10). A 10.10 plugin isNotSupportedon 12.x and vice versa. Decision needed (Q2). - 10.10.7 = .NET 8, NuGet
Jellyfin.Controller 10.10.7,targetAbi 10.10.0.0. Verified locally: the template builds against 10.10.7 with the installed .NET 8 SDK in 8 s. - Plugins are IL/AnyCPU → one build runs on x64 and the Pi5 (arm64). No native deps allowed in the plugin.
- The channel API (
IChannel+IRequiresMediaInfoCallback) exists and works in 10.10.7 and 12.3 (checked both NuGet packages). It is the only way to get a virtual, playable catalogue into every official client. Reference implementation: chaosflix-jellyfin (copy inresearch/reference-sources/chaosflix/). - Jellyfin forwards only User-Agent/Referer to ffmpeg and its relay —
never cookies or
Authorization. Therefore the plugin must proxy playback through its own ASP.NET controller with HMAC-signed anonymous URLs (chaosflix pattern). - linuxserver image paths: plugins in
/config/data/plugins/<Name>_<ver>/, config XML in/config/data/plugins/configurations/, logs/config/log/. - Listing cache 3 h (bump
DataVersion/cache key to refresh), media-info cache 5 min, one parent per item id, channelNamemust never change.
Floatplane side
- Public, no login: creator info, channel lists, post listings, thumbnails.
- Login needed:
delivery/info(stream URLs) and the HLS AES-128 key (/api/video/watchKey). Segments and playlists only need their 6-hour signed token. - Username/password login requires a Cloudflare Turnstile token — verified
today that no User-Agent bypasses it. Two real options:
- Pasted
sails.sidcookie (what the web client used in the HAR; works today, zero dependencies, lifetime unknown — must handle expiry). - OAuth2 device-code flow against Keycloak
auth.floatplane.com(what Hydravion/Wasserflug/floatplane-downloader use; access token 30 min, refresh token 90 days sliding). Needs a client_id registered by Floatplane — verifiedinvalid_clientfor an unregistered id. Obtained by asking Floatplane staff (community Discord).
- Pasted
delivery/infois rate-limited (~2/min) → cache per video until token expiry, serialise calls.scenario=downloadgives plain MP4 URLs (seekable, no key dance) — not in the capture; test once a session exists. If the token alone is enough this is the preferred playback path.
Stack
- C# / .NET 8 class library
Jellyfin.Plugin.Floatplane(switch to .NET 10- Jellyfin 12.x packages if Q2 says upgrade).
System.Text.Json,IHttpClientFactory, no third-party NuGet packages that ship natives.
- Jellyfin 12.x packages if Q2 says upgrade).
- Target OS/arch: linux-x64 (dev/test) + linux-arm64 (Pi5), single AnyCPU build.
- Tests: xUnit against recorded JSON fixtures (shapes from the HAR + spec).
- Packaging:
meta.json+ zip (+ md5) per release;manifest.jsonplugin repository served from Gitea so Jellyfin's dashboard can install/update.
Deploy target
Pattern A (playbook): Gitea Actions builds the zip on push to master,
publishes rolling latest + vX.Y.Z releases, and updates the repository
manifest.json. Install on the Pi5 by adding the manifest URL under
Dashboard → Plugins → Repositories (or scp into /config/data/plugins/).
Architecture
Jellyfin clients ──► Jellyfin server (Pi5)
│ IChannel "Floatplane"
│ root → creators → sub-channels → videos (Episode/Clip)
│ IRequiresMediaInfoCallback
│ → MediaSourceInfo { Path = /Floatplane/stream/<videoId>?t=<hmac>, MediaStreams = h264/aac from delivery meta }
│ FloatplaneStreamController (anonymous, HMAC-signed)
│ → delivery/info (cached) → rewritten HLS playlist (key URI → /Floatplane/key/…)
│ or plain MP4 relay with Range passthrough
│ FloatplaneClient (session: cookie or OAuth tokens, UA, 429 handling, delivery throttle)
│ SyncTask (IScheduledTask, every N h: subscriptions → creators → posts → cache; bumps cache key)
│ Config page: login (cookie paste now, device-code later), quality preference, which creators/channels, sync interval
└──► www.floatplane.com / cdn-vod-drm2 / pbs
Milestones
| # | Increment | Done when |
|---|---|---|
| M0 | Scaffold: csproj (net8/10.10.7), Plugin.cs, config page, meta.json, build task, unit-test project, local docker Jellyfin 10.10.7 |
done 2026-10-25 — Active in a local 10.10.7 (LSIO image) |
| M1 | FloatplaneApiClient: cookie session, user/self, subscriptions, creator/info, content listing with pagination + search/filters, post details; fixtures from the HAR |
code done — config page validates the cookie; unit tests in Jellyfin.Plugin.Floatplane.Tests |
| M2 | Channel browse: root → creators → sub-channels → videos with thumbnails, overview, duration, release date; sync task + cache key bump | done locally (web API verified: folders, paging, sorting, latest, images); Android app not yet checked |
| M3 | Playback via proxy controller: delivery/info cache + throttle, HLS playlist rewrite with key proxy (+ optional MP4 relay), MediaStreams filled so Jellyfin remuxes instead of transcoding |
code done, untested — needs a real session cookie (Bjorn, on the Pi5 or locally) |
| M4 | Packaging + CI: Gitea Actions build → zip + md5 + manifest.json → releases; install on Pi5 from the repository URL or install-pi5.sh |
code done — first CI run pending the initial push |
| M5 | Polish: watched/resume sync to Floatplane progress API, subtitles (VTT text tracks), locked-tier handling, cookie-expiry warning, Android/TV app check | next |
Roadmap / expansions (to agree)
- OAuth device-code login once Floatplane issues a client id (QR code on the config page, 90-day refresh token, no more cookie copying).
- Live streams (WAN Show):
scenario=liveIVS HLS as a live item or a Live TV tuner; offline detection by polling the playlist. - Audio posts / podcasts as an audio channel.
- Archive to library: optional scheduled download of selected channels
to
/media/Floatplane/<Creator>/<Channel>/with NFO + thumbnails for trickplay/offline (floatplane-downloader-style layout). - Multi-user: per-Jellyfin-user Floatplane sessions (
IsEnabledFor). - Home-screen "Latest from Floatplane" via
ISupportsLatestMedia. - Jellyseerr-style request/notification hook when new posts appear (notifyr on the Pi5).
First real-world test (Bjorn, when home)
- Pin the Pi5 image tag (
lscr.io/linuxserver/jellyfin:10.10.7ubu2404-ls80) before anything else, or apulllands on 12.3. - Install: Dashboard → Plugins → Repositories → add
https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/manifest.json, install "Floatplane", restart. (Orinstall-pi5.shfrom the release.) - Settings → paste
sails.sid→ Validate → Save. Creators from the subscription appear under My Media → Floatplane. - Play a 1080p video in the web client; watch
log/forFloatplaneStreamControllerlines and the ffmpeg transcode log — the goal is a stream copy (remux), not a re-encode. - Try the MP4 experiment (settings → "progressive MP4 versions") and report whether it plays; if it does it becomes the default (seekable, no key dance).
Open questions
- OAuth client id: ask Floatplane (community Discord
https://discord.gg/4xKDGz5M5B) for a device-flow client id for this plugin — only Bjorn can make that request. Until then: cookie. - Cookie lifetime: unknown; the settings page shows "rejected" when it expires. Measure in practice.
- Pi5 upgrade to 12.x: separate task; the plugin needs a .NET 10 /
12.x build then (
TaskTriggerInfoenum and package versions are the known deltas). - Live streams and the other roadmap items: priority after VOD works.