Conclusion: the fault is in Plex's build combination, not in any upstream component. Every upstream ingredient — mpv, libplacebo, the D3D11 hardware path, window embedding — works correctly when assembled the way upstream assembles it. Only Plex's assembly fails.
Workaround: one line, and it measures faster than the hardware path it replaces. Date: 2026-08-22
Press play on an HEVC file in Plex Desktop for Windows. The UI says playing. The spinner never stops. The position never leaves 0:00. There is no error, no toast, no fallback to transcoding — it just sits there indefinitely.
Server-side it looks like a healthy session. The client reports a timeline every ten seconds, and every report says the same thing:
"state": "playing", "time": 0, "playbackTime": 632494
playbackTime climbs with wall-clock. time — the actual playhead — never moves. Across
three sessions on one file I captured 128 consecutive timeline reports and every one had
time=0.
The server is perfectly happy. It reaches a direct-play decision and serves bytes:
Streaming Resource: Reached Decision id=3861 codes=(MDE=1000,Direct play OK.)
Content-Length of .../Ultraviolet (2006).mkv is 5393459606
The client pulls 155 MB — exactly the mediaBufferSize=157286 (KB) Plex asks mpv for —
fills its demuxer cache, stops reading because nothing is consuming frames, and the server
eventually logs:
Failed to stream media, client probably disconnected after 155336704 bytes: 104 - Connection reset by peer
Bytes arrive, the demuxer fills, the renderer never draws a frame.
%LOCALAPPDATA%\Plex\mpv.conf:
hwdec=noRestart Plex.
It costs nothing. Measured on the heaviest file in the library — 3840x2160 HDR10 HEVC Main 10 at 92 Mbit/s — decoded from a local copy on a Ryzen 9 9950X3D:
| decode path | fps | vs. realtime |
|---|---|---|
software (hwdec=no) |
235 | 9.78x |
d3d11va hardware |
182 | 7.56x |
Software is faster. Sixteen cores beat one NVDEC engine plus the readback. The only cost is some watts and CPU that was otherwise idle.
(Plex Desktop's Settings → Player → hardware acceleration toggle should be equivalent;
mpv.conf is what was tested.)
| Client | Plex Desktop for Windows 1.115.0.426-4e960a1d |
| Client mpv | 0.38.0, libplacebo v6.338.2, libavcodec 59.x (FFmpeg 5.1 era, patched) |
| OS | Windows 11 26200 |
| CPU | Ryzen 9 9950X3D (16C/32T) |
| GPU | RTX 5090, driver 32.0.16.1088 |
| Display | 3840x2160 @ 239 Hz, HDR on, 10-bit |
| Server | Plex Media Server 1.41.5.9626 (not implicated) |
| Test file | HEVC Main 10, 1920x1040, 10-bit, AAC 5.1, SDR, 7.6 Mbit/s |
Swapchain mpv negotiates on that panel — note it builds an HDR PQ swapchain for SDR content, because the display is in HDR mode:
vo/gpu/d3d11: Swapchain successfully configured to color space RGB_FULL_G2084_NONE_P2020 (12)
vo/gpu/d3d11: Using flip-model presentation
vo/gpu: Assuming 239.996000 FPS for display sync.
vo/gpu: Reported display depth: 10
The decisive comparison is Plex's own log, before and after hwdec=no:
hwdec on -> cplayer: VO: [gpu] 1920x1040 d3d11[p010] ... no frame, ever
hwdec off -> cplayer: VO: [gpu] 1920x1040 yuv420p10
cplayer: first video frame after restart shown (125 ms later)
Same VO, same HDR PQ swapchain, same display, same file. Software frames present through
that swapchain fine. So it is not the swapchain — it is the hardware surface handoff
into it: a D3D11 p010 texture from d3d11va never becomes a presented frame.
| Suspect | Verdict | Evidence |
|---|---|---|
| The file | cleared | ffmpeg decodes it in software and via -hwaccel d3d11va |
| Network / NAS / server | cleared | Direct-play decision reached, all ranges served, 155 MB delivered |
| Codec support | cleared | HEVC Video Extension installed; the GPU decodes HEVC Main 10 trivially |
| Multi-adapter (DisplayLink) | cleared | Window was on the discrete GPU throughout; pinning d3d11-adapter changed nothing |
| mpv version | cleared | stock 0.38.0 renders in 8/8 configurations (matrix below) |
| Every mpv option knob | cleared | colorspace-hint, swapchain-depth, output-format, gpu-next, vulkan, d3d11va-copy |
swapchain-depth regression |
cleared | mpv only reduced the default to 2 in 0.41; 0.38 already ran 3 |
Window embedding (--wid) |
cleared | stock 0.38.0 embedded in a child HWND of another app renders with d3d11va |
| libplacebo v6.338 | cleared | stock 0.37.0 ships v6.338.0-62 — the same line Plex ships — and renders |
Stock mpv 0.38.0 (the release Plex bundles), same machine, same panel, same file,
--no-config, 12s each. Verdict = did mpv log first video frame ... shown:
baseline-d3d11va RENDERS [gpu] 1920x1040 d3d11[p010] <- Plex's exact configuration
d3d11va-copy RENDERS [gpu] 1920x1040 p010
no-colorspace-hint RENDERS [gpu] 1920x1040 d3d11[p010]
output-fmt-rgb10a2 RENDERS [gpu] 1920x1040 d3d11[p010]
swapchain-depth-1 RENDERS [gpu] 1920x1040 d3d11[p010]
gpu-api-vulkan RENDERS [gpu] 1920x1040 cuda[p010]
vo-gpu-next RENDERS [gpu-next] 1920x1040 d3d11[p010]
hwdec-off RENDERS [gpu] 1920x1040 yuv420p10
Plex renders mpv into a child HWND inside its Qt/web-view application:
[MPVEngine] Property 'wid' set to '795608'
Every test above gave mpv its own top-level window, so this was the last structural
difference. Reproduced it: a bare WinForms host window with a real message pump, mpv
embedded via --wid, visible on the same HDR panel, frames confirmed on screen by eye.
control, hwdec=no RENDERS [gpu] 1920x1040 yuv420p10
mpv 0.38.0 + d3d11va RENDERS [gpu] 1920x1040 d3d11[p010]
mpv 0.37.0 + d3d11va RENDERS [gpu] 1920x1040 d3d11[p010] (libplacebo v6.338.0)
Embedding is not the cause. Neither is libplacebo v6.338.
| Build | mpv | libplacebo | libavcodec | embedded + d3d11va |
|---|---|---|---|---|
| Plex | 0.38.0 | v6.338.2 | 59.x — FFmpeg 5.1, patched | STALLS |
| stock | 0.37.0 | v6.338.0-62 | 60.35 — FFmpeg 6.1 | RENDERS |
| stock | 0.38.0 | v7.349.0 | 61.5 — FFmpeg 7.0 | RENDERS |
Each row shares components with Plex's. Only Plex's combination fails.
The one ingredient with no stock counterpart is Plex's FFmpeg. libavcodec 59 is the
FFmpeg 5.1 line (mid-2022), and Plex patches it. D3D11 hardware frames are allocated by
FFmpeg's libavutil/hwcontext_d3d11va.c; libplacebo then wraps those textures for
rendering. If FFmpeg 5.1 allocates the texture array with different bind or misc flags than
6.x+ does, libplacebo's wrapping can fail — and "hwdec initialises, VO reconfigures, no
frame ever, no error" is exactly what that looks like. No stock build pairs a v6.338
libplacebo with a 5.1-era FFmpeg, so the combination cannot be reproduced from
off-the-shelf parts.
A secondary difference, less likely to affect presentation but worth naming: Plex does not
use mpv's native stream layer. It registers its own via mpv_stream_cb_add_ro and feeds
data through its own curl:
stream_callback: Opening https://...
curl: Opening https://...
Either way this is Plex's to fix, not upstream's. Every upstream component behaves correctly in its own context.
Worth documenting because it looks viable right up until it isn't.
Plex ships mpv as a separate LGPL shared library, exactly as LGPL §6 relinking requires:
-Dcplayer=false -Dlibmpv=true -Dgpl=false -Ddefault_library=shared
So you are entitled to replace it, and the symbol surface checks out — all 54 of Plex's exports exist in a current build, none missing. Plex even starts on it:
Starting Plex version: 1.115.0.426-4e960a1d
[MPVEngine/mpv] cplayer: mpv v0.41.0-920-gdd5d17d32
[MPVEngine/mpv] cplayer: libplacebo version: v7.371.0
Then every stream fails to open with An unknown error occurred (4294967283) — that is
-13 unsigned, MPV_ERROR_LOADING_FAILED. The reason:
cplayer: Setting option 'stream-lavf-o' = 'resolve_hosts=[192-0-2-10.<hash>.plex.direct:192.0.2.10]'
curl: error: Could not resolve hostname
stream: Failed to open https://192-0-2-10.<hash>.plex.direct:32400/video/:/transcode/universal/start
resolve_hosts is not a vanilla FFmpeg option. Plex patches FFmpeg with it so
*.plex.direct hostnames resolve to the LAN IP encoded in the name without a DNS
round-trip. Plex's libmpv-2.dll dynamically imports Plex's patched avcodec-59.dll /
avformat-59.dll; stock libmpv builds statically link vanilla FFmpeg and ignore them.
The option evaporates, real DNS is attempted, nothing opens.
This is also the clue that led to the conclusion above: the patched FFmpeg is not incidental packaging, it is load-bearing, and it is the component the working builds do not share.
(Back up first; restore with Copy-Item "$dst.backup" $dst -Force in an elevated shell.)
Everything here came from two log files. No debugger, no special build.
Client: %LOCALAPPDATA%\Plex\Logs\Plex.log
cplayer: VO: [gpu] ... <- the pixel format names your path
first video frame after restart shown <- absent = nothing was ever drawn
d3d11[p010] = zero-copy hardware surface. yuv420p10 = software. The presence or absence
of that second line is the entire diagnosis.
Server: .../Plex Media Server/Logs/Plex Media Server.log — if time=0 repeats while
playbackTime climbs, the client is lying about playing.
Is it your file? ffmpeg -v error -hwaccel d3d11va -i FILE -t 5 -f null -. A clean exit
means the file and your decoder are fine and the problem is downstream of both.
Three traps cost real time. All three produced plausible wrong answers rather than obvious errors, which is the dangerous kind.
1. Occluded windows invalidate presentation tests. The first option matrix launched mpv minimised, to be unobtrusive. A minimised or occluded window with a flip-model swapchain may legitimately never present, so every case reported "stalled" — including the known-good control. Any test of a presentation path needs a genuinely visible window.
2. A control case that must pass. Adding one is what caught trap 1 and trap 3 within seconds each. Without it, both would have been written up as findings.
3. Backslash escaping asymmetry. In this tooling the shell channel applied one extra round of backslash-unescaping and the PowerShell channel did not:
written shell receives PowerShell receives
\ \ \
\\ \ \\
\\\\ \\ \\\\
A UNC path written \\server\share arrived as \server\share, mpv silently failed to open
the file, and a wrapper timeout reported that as a stall — the exact symptom under
investigation. It produced a confident, completely wrong "stock mpv reproduces the bug"
result that pointed at the opposite conclusion, and survived until the control caught it.
The general lesson: when your harness can produce the same observable as your bug, add a control that must succeed, and be most suspicious of the first result that confirms what you already believed.
The stall needs, as far as this investigation could establish: Plex Desktop for Windows,
HEVC with hardware decode active, and a D3D11 flip-model swapchain. It reproduced on every
HEVC title tried (34 of 42 in the library) and never on H.264. Whether the high-refresh HDR
panel is necessary or merely incidental was not isolated — dropping the display to 60 Hz or
turning HDR off, with hwdec re-enabled, would settle that and is the cheapest remaining
experiment.