The streaming landscape is broken. Not in the way most people talk about it. Not the price hikes or the password crackdowns. It is broken at the experience level. A sports fan in 2026 needs ESPN+, Peacock, Paramount+, YouTube TV, Amazon Prime, and probably two more services next season. That is six different apps, six different interfaces, six different billing cycles, six different places to look for a game. For a 30-year-old who grew up with smartphones, it is annoying. For an 88-year-old who just wants to watch LSU play on Saturday afternoon, it is impossible.
The Alternative
There is another way. A self-hosted media server fed by a single IPTV subscription, running on a Mac mini in a Docker container. One app on the TV. One login. One curated channel guide. No ads, no upsells, no interface redesigns every quarter. You open the app, you see your channels, you press play.
The server is Emby, running in Docker on a Mac mini M4 using the linuxserver/emby image. The IPTV source is Primestreams, delivering roughly 3,300 channels via a standard M3U playlist. The client is a Roku Streambar SE, the cheapest Roku device you can buy. Total hardware cost for the streaming endpoint: about $30.
The Use Case
The goal was simple. Set up a curated sports streaming guide for my grandparents in south Louisiana. They are in their late 80s. They want to watch the Saints, LSU, the Astros, and whatever ESPN is showing. They do not want to learn six apps or remember six passwords.
I built them a 25-channel favorites list inside Emby. Nine sports networks: ESPN, ESPN2, ESPNU, ESPN News, Fox Sports 1, Fox Sports 2, CBS Sports Network, NFL Network, NFL RedZone. Six conference networks: SEC Network, ACC Network, Big Ten Network and three BTN overflow channels for when multiple games air at the same time. Two Houston channels: Space City Home Network and a dedicated Astros feed. Two New Orleans locals: WWL (CBS) and WVUE (Fox). Six SEC+ overflow slots that carry extra games during football and basketball season.
On the Roku, they open Emby, go to Live TV, and the guide shows only their 25 channels. They pick one and press play. That is the entire user experience.
The Bug
It worked. Then it did not.
During testing, some channels played perfectly on the Roku while others buffered forever or went black. No pattern. CBS (WWL) New Orleans would play fine, then Big Ten Network would spin. ESPN would load, then fail five minutes later. The Emby web client on a laptop played every channel without issues. The problem was specific to Roku.
This led down a rabbit hole that ended at a systemic failure in how the linuxserver Docker image packages Emby's ffmpeg dependencies for ARM64 on Apple Silicon.
The Root Cause
Two independent failures were stacked on top of each other, and both had to be solved.
Problem 1: LD_LIBRARY_PATH poisoning. When Emby launches ffmpeg or ffprobe inside the container, it injects its own LD_LIBRARY_PATH into the child process environment. On x86_64, this works fine. On ARM64 (Apple Silicon running Docker Desktop), the inherited loader environment causes Emby's bundled ffmpeg binary to pick up the wrong shared libraries at startup. The process crashes or hangs silently. Emby logs show No video encoder found for 'h264'. The VideoEncoders list in hardware detection comes back empty. Live TV never starts.
Problem 2: Copy-codec HLS stalls. Even when ffmpeg manages to start, Emby's Roku Live TV path selects -c:v:0 copy -c:a:0 copy for HLS output. This tells ffmpeg to pass the video and audio streams through without re-encoding. On x86_64, this works. On ARM64, these copy-codec jobs produce empty or malformed .ts segments. The Roku requests the HLS playlist, gets segment entries, requests the segments, and gets nothing usable. It buffers forever.
I searched everywhere. Emby forums, Reddit, GitHub issues, Docker community boards. I ran a Perplexity deep research session that crawled every relevant thread. The consensus was uniform: Emby on ARM64 with Roku clients does not work reliably for Live TV. Switch to Jellyfin. Use a different client. Run on x86. Give up.
The Insight
The breakthrough came from understanding that Emby's codec detection path and its codec execution path are completely independent.
When Emby starts, it runs ffdetect to probe its bundled ffmpeg binary for capabilities. This populates the VideoEncoders list and sets IsEmbyCustom: true. This detection step works correctly on ARM64 as long as the binary itself is intact.
The failure happens later, at execution time, when Emby actually launches ffmpeg to transcode a live stream. That is where the poisoned LD_LIBRARY_PATH causes crashes, and where the copy-codec selection causes Roku stalls.
You do not need to fool ffdetect. You do not need to patch Emby binaries or replace DLLs. You keep the detection path happy by leaving Emby's bundled binaries exactly where they are. You only intercept the live transcode calls that happen after detection, cleaning the environment and rewriting the codec.
The Fix
The solution is a single shell script that runs automatically when the container starts, using the linuxserver custom-cont-init.d hook.
On first run, the script backs up Emby's real ffmpeg and ffprobe ELF binaries to a safe location (/usr/local/bin/emby-ffmpeg-real and /usr/local/bin/emby-ffprobe-real). It then replaces the originals with bash wrapper scripts.
The ffprobe wrapper is straightforward. It clears the inherited environment, sets up the correct LD_LIBRARY_PATH pointing to Emby's own lib directories, and launches the real binary through the ARM64 dynamic loader (ld-linux-aarch64.so.1). No argument rewriting needed.
The ffmpeg wrapper does the same environment cleanup, but adds one critical behavior: it scans the argument list for the live directstream pattern (-c:v:0 copy paired with -c:a:0 copy). When it finds this pattern, it rewrites the arguments to use real transcoding instead:
- Video:
libx264withsuperfastpreset,zerolatencytune, short GOP (60 frames), no B-frames, CRF 23 - Audio:
aacat 128kbps stereo
The low-latency tuning is deliberate. Roku needs the first HLS segment fast or it times out. The superfast preset and zerolatency tune eliminate lookahead buffering. The short GOP means seek points arrive quickly. No B-frames means every frame can be decoded independently. The result is a cold-start playlist response in about 3 seconds, down from the 5+ seconds that caused the original timeouts.
The wrappers are recreated on every container start, so the fix survives image updates, container recreates, and Docker restarts. No binaries are modified. No DLLs are patched. Emby's detection path still sees its own custom ffmpeg build and reports IsEmbyCustom: true with a full encoder list.
The Result
Every channel plays. Every time.
Big Ten Network, which was the most consistent failure during testing, now produces valid HLS segments within 3 seconds of pressing play. CBS (WWL) New Orleans loads cleanly. ESPN, SEC Network, Fox Sports, NFL Network, all 25 curated channels work on the Roku without buffering, without errors, without intervention.
My grandparents open the Emby app on their Roku. They see their 25 channels. They pick one and press play. That is the entire interaction. No buffering, no error messages, no confusion. One login, one guide, one button.
Open Source
The fix is published on GitHub as a drop-in solution for anyone running the linuxserver Emby image on Apple Silicon with Roku clients:
GitHub: github.com/shiz504/emby-roku-as-fix
One script. One mount. Works with the stock linuxserver/emby image. No custom Docker builds required.
The repo includes the fix script, an example Docker Compose file, a detailed README explaining the root cause and the fix, and a step-by-step validation guide with API-level tests you can run to confirm the fix is working.
It was tested end-to-end on a completely clean Emby container. Not my production setup. A fresh pull of lscr.io/linuxserver/emby:latest on ARM64, with a new config directory, a fresh M3U tuner, and no existing state. Three channels were validated: Big Ten Network, ESPN, and CBS (WWL) New Orleans. All three produced valid HLS .ts segments. Zero encoder errors. The fix was confirmed to survive container restarts.
The Bigger Picture
Self-hosted streaming is not about piracy or cutting corners. It is about control.
When you host your own media server, you control the interface. You control the channel lineup. You control the user experience. There are no ads injected into the guide. There are no upsell banners between channels. The interface does not redesign itself every six months because a product manager needed to justify a sprint. You build it once and it runs until you decide to change it.
For my grandparents, this means they will never have to learn a new app. The Emby interface on their Roku will look the same next year as it does today. Their 25 channels will be in the same order. The play button will do the same thing. When LSU plays on Saturday, they will find SEC Network in the same place it has always been.
That is what technology should do. It should make things simpler for the people who use it, not more complicated. It should disappear into the background and let people watch football.
The future of TV is not another app. It is your own infrastructure, serving your own household, on your own terms.
Credits
The fix was built and validated with the help of two AI coding agents: OpenAI Codex CLI for the initial root cause analysis and wrapper development, and Anthropic Claude Code CLI for the end-to-end testing, sanitization, GitHub packaging, and this article. The combination of human engineering intuition and AI agent execution made it possible to diagnose, fix, test, and publish the solution in a single session.
If you are running Emby on Apple Silicon and Roku Live TV is broken, the fix is free and open source. Clone the repo, copy one file, add one line to your compose file, restart. That is it.