Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
Some checks failed
release / build-release (push) Has been cancelled
Some checks failed
release / build-release (push) Has been cancelled
- 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>
This commit is contained in:
152
doc/plan.md
Normal file
152
doc/plan.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# 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/floatplane-api-research.md),
|
||||
[`research/jellyfin-plugin-research.md`](research/jellyfin-plugin-research.md),
|
||||
HAR findings in [`floatplane-api-notes.md`](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.
|
||||
Reference in New Issue
Block a user