← Back to blog

Getting a Paid TV Package Somewhere the Vendor Never Intended: Magio GO in Jellyfin

23 July 2026 · 4 min read

self-hostedhomelab

Not a story about writing code from scratch — a story about everything that still needs solving once "someone already solved this" turns out to be only the beginning, not the end.

Why bother with a bridge that doesn't exist

The goal was simple on paper: have TV channels from my own paid Magio GO subscription (Slovak Telekom's IPTV service) sitting alongside the rest of the home media library in Jellyfin — one app for movies, shows, and live TV, no switching between apps. Reality: Jellyfin has no official or built-in support for Magio GO at all. There's simply no bridge between them — you have to build one yourself.

The first step wasn't writing a solution from scratch. It was an ordinary bit of research — and that turned up someone on GitHub who'd already tackled the problem of "how do you get a Magio GO stream into a format ordinary IPTV clients understand" (cvb941/magiogo-iptv-server). Deploying someone else's existing project instead of building one from the ground up was a deliberate choice — no need to rediscover how the Magio GO API authenticates and how to pull a playlist and EPG out of it, when someone had already solved and shared that.

Where this stopped being "just deploy it and you're done"

The Docker deployment went fast — the container runs, generates an M3U playlist and an XMLTV EPG, Jellyfin can load it as a Live TV tuner. The real work only started once it became clear that playback itself wasn't reliable. Months of iterative debugging (from March through July) worked through a chain of related problems, one after another — caching behavior, how the app holds its connection to the Magio server, quality and audio-track selection, retrying failed requests.

The most interesting and technically deepest of them deserves its own explanation: the Magio CDN authorizes individual video/audio fragments via a session cookie set on the manifest response — the manifest URL itself does carry a signed query string, but the relative segment paths inside the manifest (DASH SegmentTemplate) don't; those rely solely on that cookie. The problem: ffmpeg's DASH demuxer (which Jellyfin, and many other players, are built on) resolves those relative paths against the originally requested URL, not the one it got redirected to (HTTP 303) for the real CDN manifest. The result: a plain redirect to the real CDN manifest broke fragment fetching with an "Invalid data found when processing input" error — playback would freeze or never start at all.

The fix wasn't tuning ffmpeg parameters (that demuxer behavior isn't something you can simply switch off) — it was building a proxy layer of its own: the app downloads the real manifest itself, rewrites its BaseURL to point back at itself, and forwards every fragment request onward to the real CDN with that same captured cookie attached. That sidesteps both problems at once — the demuxer never seeing the redirect, and the segments lacking their own authorization.

A few months later: devices quietly vanishing

In mid-July, a different, seemingly unrelated problem showed up: the app stopped working with an error about exceeding the account's connected-device limit. A Magio account only allows a limited number of devices logged in at the same time.

The real cause was the deployment itself, not the app: the Docker container had no persistent volume mounted for its session-data folder. The app did save its login session to disk inside the container, but without a volume mount, that session died with every rebuild/restart of the container — and on every such restart, the app simply logged in again, registering a new device on the account. Over a few months of small tweaks and restarts, this quietly ate up the entire available device limit, until one of the occupied slots wasn't even this app — it was a real mobile device in the household that couldn't be touched.

The fix was two steps: using the app's own internal device-logout function to free up the slot occupied by that specific old, orphaned entry (not the mobile device), and adding a persistent volume mount for the session-data folder to docker-compose.yml — so the next container restart wouldn't start over with a fresh registration again.

What to take away

  • "Someone already solved this" is a good starting point, not a finished solution. The base app worked, but reliable playback took months of chasing down details that only surface under real use.
  • A player error doesn't have to be a stream error. ffmpeg/Jellyfin failing to play fragments wasn't about corrupted video — it was a mismatch between how the CDN authorizes requests and how the demuxer interprets redirects.
  • Missing persistent state in a container can sometimes surface months later, and somewhere you wouldn't expect. A session lost on every restart looked insignificant, until it built up into an exhausted device limit on the account.
  • When troubleshooting "why did this stop working," first check what actually changed in the infrastructure around the app — not just in the app itself. The cause was in how it was deployed (a missing volume), not in the app's logic.

Slovak and Czech TV channels now run inside Jellyfin without switching apps — exactly the original goal, just with considerably more layers underneath than it looked like at first glance.