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 (linuxserver/emby specific). The linuxserver/emby container image does not set LD_LIBRARY_PATH at the container environment level. When Emby launches ffmpeg or ffprobe as a child process and injects its own library paths, the inherited loader environment on ARM64 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. This issue does not affect the official emby/embyserver_arm64v8 image, which correctly sets LD_LIBRARY_PATH=/lib:/system at the container level.
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. The copy-codec passthrough preserves whatever the source stream delivers, including variable GOP structures, high H.264 levels, and non-standard timing that Roku's HLS client cannot always handle. The Roku requests the HLS playlist, gets segment entries, but playback stalls, buffers indefinitely, or never starts. This second issue may affect both the linuxserver and official Emby images, since both select the same copy-codec directstream path for Roku Live TV.
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. 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.
Why Not Just Disable Direct Streaming
The Emby community and dev team have historically recommended a blanket workaround for Roku Live TV issues: disable direct streaming globally in the server settings. This forces every stream through full transcoding regardless of whether the source is compatible with the client.
That approach has problems. It forces transcoding on streams that would play perfectly fine as passthrough, wasting CPU cycles on a machine that could be doing other work. It produces inconsistent results because the transcode parameters Emby selects by default are not tuned for Roku's specific HLS requirements. And it applies to every client, not just Roku, degrading the experience for web and mobile users who never had an issue in the first place.
The wrapper fix is surgical. It only intercepts the specific -c:v:0 copy -c:a:0 copy calls that break Roku. If Emby selects a different codec path for a given stream, the wrapper passes it through untouched. If a non-Roku client requests playback, nothing changes. The rewrite only fires on the exact failure pattern, and when it does, it uses low-latency tuning specifically chosen for Roku's HLS parser. Everything else runs exactly as Emby intended.
Official Image Testing
Testing against the official Emby ARM64 image (emby/embyserver_arm64v8:latest) confirmed that the LD_LIBRARY_PATH is set correctly on that image. ffmpeg launches cleanly, IsEmbyCustom reports true, and all five video encoders are detected. The LD_LIBRARY_PATH poisoning issue is specific to the linuxserver container packaging, not to Emby itself.
Update: Real Roku Testing Confirms Both Images Affected (April 11, 2026)
On April 11, 2026, we ran the test the original article said was pending: a real Roku pointed at the stock official Emby Docker image on ARM64, with zero patches applied. The results changed the story.
A dedicated test container was deployed on the same Mac mini M4 using emby/embyserver_arm64v8:latest on port 8097, running alongside the production linuxserver container on port 8096. The same Primestreams M3U tuner and XMLTV guide were configured. A papa test user was created with the same 25-channel favorites. A temporary port forward was opened on the UDM-SE gateway to allow WAN access from the test location.
The Roku Streambar SE (firmware DVP-15.1) was at my grandparents' house in Metairie, Louisiana, connected to their local AT&T Fiber. It connected to the test instance at wan1.intrac.net:8097. Login worked. All 25 channels appeared. Favorites loaded correctly.
Then the tests began.
Fox News played perfectly. Emby selected -c:v:0 copy -c:a:0 copy because the source bitrate fit under Roku's 24.8 Mbps cap. The first live.m3u8 response came back in 57 milliseconds. The Roku started playback immediately and the stream was stable. This is the direct-stream path: no CPU work, instant segment generation, Roku is happy.
Big Ten Network failed immediately. Spinning circle, both attempts. The server logs told the full story. Emby detected the source bitrate at 40 Mbps, exceeding Roku's maxstreamingbitrate of 24,806,204. This forced Emby out of the copy-codec path and into full libx264 software transcoding. The ffmpeg command used -preset:v:0 veryfast, which is Emby's default for ARM64.
On the first attempt, the first HLS segment took 5,515 milliseconds to produce. That is 5.5 seconds of the Roku staring at a blank screen waiting for data. The Roku received the segment, probed it with a range request, then immediately killed the session. On the second attempt, the Roku disconnected even faster. ffmpeg was killed with SIGKILL (exit code 137) before it could produce a single complete segment.
This was the smoking gun. The problem is not LD_LIBRARY_PATH. The problem is not copy-codec passthrough. The problem is that ARM64 software transcoding with Emby's default veryfast preset is too slow for Roku's HLS client.
Roku's HLS implementation has an aggressive cold-start timeout of roughly 3 to 4 seconds. If the first playable segment does not arrive within that window, the Roku concludes the stream is dead and kills it. On x86 hardware with Quick Sync or NVENC, veryfast transcoding is fast enough. On ARM64 with software-only encoding, it is not. The M4's CPU is fast for ARM, but software libx264 encoding at 720p60 with the veryfast preset still takes over 5 seconds to produce the first 3-second segment. That is past Roku's patience.
The wrapper fix solves this by using superfast instead of veryfast, adding -tune zerolatency to eliminate lookahead buffering, shortening the GOP to 60 frames (1 second instead of 3), and disabling B-frames. This brings first-segment latency down to approximately 1.5 seconds. Well within Roku's tolerance.
Channels that stay under Roku's bitrate cap play fine on both images because they use copy/copy and never touch the CPU. Channels that exceed the cap, which includes most sports networks during live broadcasts, hit the transcode path and fail without the fix.
This means the fix addresses two independent bugs:
- Bug 1 (linuxserver only):
LD_LIBRARY_PATHpoisoning prevents ffmpeg from launching. The wrapper's clean-env launch fixes this. The official image is not affected. - Bug 2 (both images): ARM64
libx264transcoding withpreset veryfastproduces first segments too slowly for Roku. The wrapper'ssuperfast+zerolatencytuning fixes this. Both images are affected.
The recommendation to the Emby development team: detect the ARM64 + Roku client combination and adjust the transcode preset to superfast with zerolatency tuning. The current veryfast preset works on x86 where hardware encoders are available, but it is too slow for ARM64 software encoding under Roku's tight HLS timeout window.
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 April 2026 real Roku testing and official image validation were performed with Claude Code Opus, which deployed the test container, configured the tuner and user accounts via Emby's API, opened the UDM-SE port forward, and analyzed the server logs in real time while the Roku tests were running at a remote location.
If you are running Emby on Apple Silicon with Roku clients and Live TV is broken, the fix is free and open source. For the linuxserver image: clone the repo, copy one file, add one line to your compose file, restart. For the official Emby image: the same transcode tuning is needed, though the delivery mechanism differs since the official image does not support custom-cont-init.d. The underlying issue is an Emby core decision about transcode presets on ARM64, and the Emby team has been notified.