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 GET
to /system/proxy
, then a POST
to /.../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 POST /.../YouTube/PlayVideo
.
Step 5: Go back in Charles, and again compare a working and non-working request, this time for GET /system/proxy
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 gzip, deflate
.
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, POST /.../YouTube/PlayVideo
.
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!