Lightweight, vibe coded middleware so I can watch Pluto TV in Jellyfin
- JavaScript 99.7%
- Dockerfile 0.3%
|
|
||
|---|---|---|
| src | ||
| test | ||
| .dockerignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| FFmpeg.Remux-2026-05-31_19-46-13_6b585735204e514473070aafe4e7114f_842ec3ea.log | ||
| package.json | ||
| README.md | ||
Pluto Jellyfin Proxy
Stable local M3U and XMLTV endpoints for using Pluto TV with Jellyfin Live TV.
The playlist exposes stable remuxed MPEG-TS URLs like:
http://localhost:8080/stream/<channel-id>/remux.ts
The application creates and refreshes Pluto web sessions internally, rewrites HLS manifests, and keeps JWTs invisible to Jellyfin. The default M3U points clients at a server-side ffmpeg remux endpoint that copies audio/video without transcoding and outputs MPEG-TS.
Run locally
npm test
npm start
Endpoints:
http://localhost:8080/playlist.m3u
http://localhost:8080/xmltv.xml
http://localhost:8080/healthz
http://localhost:8080/probe/<channel-id>
http://localhost:8080/stream/<channel-id>/master.m3u8
http://localhost:8080/stream/<channel-id>/remux.ts
Podman / Docker
podman build -t pluto-jellyfin-proxy .
podman run --rm -p 8080:8080 -e PLUTO_REGION=CA pluto-jellyfin-proxy
In Jellyfin, add http://HOST:8080/playlist.m3u as the M3U tuner and http://HOST:8080/xmltv.xml as the XMLTV guide.
Configuration
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP listen port |
HOST |
0.0.0.0 |
HTTP listen address |
PUBLIC_URL |
auto-detected | External base URL used in generated playlists |
PLUTO_REGION |
CA |
Pluto country/marketing region |
GUIDE_HOURS |
12 |
Hours of XMLTV guide data to request |
CHANNEL_CACHE_MS |
3600000 |
Pluto channel list cache lifetime |
EXCLUDE_CHANNEL_IDS |
empty | Comma-separated Pluto channel IDs to hide from M3U/XMLTV output |
PLAYABLE_CHANNEL_FILTER |
false |
Diagnostic/experimental: probe streams while building M3U/XMLTV; probes fail open so transient Pluto failures do not hide channels |
PLAYABLE_CHANNEL_CACHE_MS |
3600000 |
Diagnostic playability probe cache lifetime |
GUIDE_CACHE_MS |
1800000 |
XMLTV guide cache lifetime |
PROXY_TOKEN_TTL_MS |
172800000 |
Lifetime for local proxy token mappings, default 48 hours |
SESSION_REFRESH_SKEW_MS |
900000 |
Refresh Pluto sessions before token expiry |
PLAYLIST_STREAM_PATH |
remux.ts |
Stream path used in generated M3U entries; set to master.m3u8 for direct HLS |
FFMPEG_PATH |
ffmpeg |
ffmpeg binary used by /stream/<channel-id>/remux.ts |
REMUX_IDLE_RESTART_MS |
4000 |
Restart server-side ffmpeg if an active remux emits no MPEG-TS bytes for this long |
REMUX_START_TIMEOUT_MS |
20000 |
Restart server-side ffmpeg if it produces no initial MPEG-TS bytes for this long |
REMUX_RESTART_DELAY_MS |
1000 |
Delay before restarting server-side ffmpeg after an exit or watchdog kill |
REMUX_STARTUP_FILLER_MS |
1000 |
Valid black/silent MPEG-TS duration emitted when upstream startup fails before real packets arrive |
PLUTO_USER_AGENT |
Chrome-like UA | User agent sent to Pluto |