Files
jellyfin-plugin-floatplane/doc/plan.md
claude 9e33a343ab
Some checks failed
release / build-release (push) Has been cancelled
Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
- 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>
2026-10-25 09:42:27 +01:00

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:latest is now 12.3 (.NET 10). A 10.10 plugin is NotSupported on 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 in research/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, channel Name must 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:
    1. Pasted sails.sid cookie (what the web client used in the HAR; works today, zero dependencies, lifetime unknown — must handle expiry).
    2. 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 — verified invalid_client for an unregistered id. Obtained by asking Floatplane staff (community Discord).
  • delivery/info is rate-limited (~2/min) → cache per video until token expiry, serialise calls.
  • scenario=download gives 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.
  • 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.json plugin 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=live IVS 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)

  1. Pin the Pi5 image tag (lscr.io/linuxserver/jellyfin:10.10.7ubu2404-ls80) before anything else, or a pull lands on 12.3.
  2. Install: Dashboard → Plugins → Repositories → add https://gitea.brasse-pc.eu/brasse/jellyfin-plugin-floatplane/releases/download/latest/manifest.json, install "Floatplane", restart. (Or install-pi5.sh from the release.)
  3. Settings → paste sails.sid → Validate → Save. Creators from the subscription appear under My Media → Floatplane.
  4. Play a 1080p video in the web client; watch log/ for FloatplaneStreamController lines and the ffmpeg transcode log — the goal is a stream copy (remux), not a re-encode.
  5. 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

  1. 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.
  2. Cookie lifetime: unknown; the settings page shows "rejected" when it expires. Measure in practice.
  3. Pi5 upgrade to 12.x: separate task; the plugin needs a .NET 10 / 12.x build then (TaskTriggerInfo enum and package versions are the known deltas).
  4. Live streams and the other roadmap items: priority after VOD works.