Fixing Plex issue with Watch Later YouTube videos
For quite some time now, Plex has been plagued with a bug preventing Google Chrome users from watching YouTube videos they saved into their Watch Later queue.
Being a fan of Chrome (I’m not using anything else really), Plex, and their Watch Later queue, which I populate using both IFTTT (widget: IF new RSS item THEN send email to [my_plexit_email_address]) and their Plex It! bookmarklet, I decided to further debug the issue.
Here’s how I found the problem, and a workaround that all end-users can use, until the Plex team releases a fix.
Step 1: Check the Plex Media Server (PMS) logs, to see if any errors could point out the problem.
What I learned: The web app sends an
/system/proxy, then a
/.../YouTube/PlayVideo, then nothing for 20 seconds, then
We didn't receive any data from 127.0.0.1:some_random_port in time, dropping connection.
I also found a bunch of data/prefs/cache files with YouTube in their name; deleting all of them didn’t help.
Step 2: Use the Chrome Developer Tools to see HTTP requests being sent, and what might be wrong in there.
What I learned: The
GET /system/proxy was returning a response in a few ms, but the
POST /.../YouTube/PlayVideo was left hanging; the server was not returning any response to that HTTP request.
Step 3: Use Charles Proxy to compare a working
POST /.../YouTube/PlayVideo request, as sent by Safari, versus a non-working request, sent by Chrome.
What I learned: Safari is sending HTML text in the body of the POST, while Chrome is sending binary data, which I believe is the same HTML, but gzipped. That might not be an issue for HTTP responses, since all web clients can decompress gzipped content, but the Plex web app sometimes sending text, and sometimes gzipped data, that is most probably where the problem comes from. I doubt PMS is expecting either, so that’s why it works when Safari sends text, and it doesn’t work when Chrome sends gzipped data.
Step 4: Find where the HTML text it sends comes from. Charles Proxy is still my friends here.
What I learned: The only HTTP request made before trying to play the video is
GET /system/proxy. And evidently, the response of that request is what it getting sent to
Step 5: Go back in Charles, and again compare a working and non-working request, this time for
What I learned: The only difference in the HTTP requests was the browser identifiers (User-Agent and what-not), and a very small addition to the
Accept-Encoding HTTP header sent by Chrome:
gzip, deflate, br, while Safari was only sending
Step 6: Use cURL on the command line to try to send a working and a non-working requests.
What I learned: After a few tries, I noticed that when sending
gzip, deflate, br in the
Accept-Encoding header, the returned response was NOT decompressed by cURL.
In reality, it was decompressed, but it seems that the server was sending back double-compressed data… Which was quite evident, from the
X-Plex-Content-Original-Length header returned then; it was almost the same size as the
X-Plex-Content-Compressed-Length, which was not the case, when
Accept-Encoding: gzip, deflate was sent instead.
So I found the culprit:
Accept-Encoding: br, whatever it is supposed to do, is causing the
GET /system/proxy to return double-compressed data, which the web app JS code was receiving decompressed only once, and was thus sending still compressed to the next HTTP request,
Final step: Confirm the fix: removing
br from the
Accept-Encoding header in the
GET /system/proxy request, using a Chrome extension that allows me to modify the HTTP headers sent for any HTTP request.
I tried a few extensions, and was happy with the options given to me by the Requestly extension.
I configured a rule to modify the
Accept-Encoding header, for requests with
path = /system/proxy, like so:
Final Final step: Let the Plex team know what the problem is, and post the workaround for end-users that might not be patient enough.
P.S. Looks like
Accept-Encoding: br, AKA Brotli, is a new compression method enabled in Chrome starting in v50. “Advantages of Brotli over gzip: - significantly better compression density - comparable decompression speed” - Ref
This would explain why I was never able to
gunzip the data Chrome received, in the response to
GET /system/proxy; it was compressed using a very funkily-named compression method!