Lightweight, vibe coded middleware so I can watch Pluto TV in Jellyfin
  • JavaScript 99.7%
  • Dockerfile 0.3%
Find a file
2026-05-31 20:03:47 -04:00
src Add FFmpeg remux support for HLS proxy 2026-05-31 20:02:46 -04:00
test Add FFmpeg remux support for HLS proxy 2026-05-31 20:02:46 -04:00
.dockerignore Add Pluto Jellyfin proxy service 2026-05-28 00:17:18 -04:00
docker-compose.yml Resolve Pluto streams more resiliently 2026-05-28 02:21:34 -04:00
Dockerfile Add FFmpeg remux support for HLS proxy 2026-05-31 20:02:46 -04:00
FFmpeg.Remux-2026-05-31_19-46-13_6b585735204e514473070aafe4e7114f_842ec3ea.log Add FFmpeg remux support for HLS proxy 2026-05-31 20:02:46 -04:00
package.json Add Pluto Jellyfin proxy service 2026-05-28 00:17:18 -04:00
README.md Add FFmpeg remux support for HLS proxy 2026-05-31 20:02:46 -04:00

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