Quick background: I am writing a browser extension that manipulates video while it’s being played in a browser. The script itself is supposed to be as general purpose as it gets, and it should run on any site that has a video on it.
My video manipulations rely on being able to manipulate pixel data from the video. The HTML5-blesed way to do get video pixels into something you can work with in javascript is canvas2dContext.drawImage()
function to draw the current video frame to a canvas, and context.getImageData()
to get said data.
The current code boils down to (I am simplifying things a fair bit):
let video = document.findElementsByTagName('video')[0]; let canvas = document.createElement('canvas'); let context = canvas.getContext('2d'); handleVideoFrame() { context.drawImage(video, 0, 0, canvas.width, canvas.height); let data = context.getImageData(0,0,canvas.width, canvas.height); processData(data); } processData(data) { // a lot of heavy calculations going on here } window.requestAnimationFrame(handleVideoFrame);
The Problem: DRM
This works well and good, until you try to do this on Netflix, Disney+ or a different site of the same caliber. Those sites use DRM, and if there’s DRM on that video, context.drawImage()
will not work. Note that I do not wish to capture the actual frames of DRM protected videos, but I want to know whether the DRM is there.
In Firefox, this is not a big deal, because if you try to call context.drawImage()
on a DRM-protected video, you will get an exception. You can then catch that exception, don’t run the heavy processData()
, alert the user to the fact that your script won’t be working on that site because of DRM, and then shut the entire thing down.
In Chrome (and other Chromium reskins for that matter), on the other hand, context.drawImage()
will not fail. Instead, context.drawImage()
will draw a 100% opaque black square without throwing an exception, and this is a problem because:
- you can never definitely tell whether the video is DRM-protected or not
- therefore you cannot inform the user, who will blame your script
- and while you can check whether the frame is black and avoid calling the heavy
processData()
if it is, you’re still doingdrawImage()
calls that you don’t need to be doing
Solutions that I’ve tried
context.getImageData()
returns an object that contains an array with RGBA values for each pixel. I initially hoped that I could determine whether the video was being DRM-protected by looking at alpha values. However, the frame drawn bydrawImage()
is always¹ completely opaque, which means that this is a dead end.
¹ Unless the video hasn’t been loaded yet, in that case it’s transparent.
Solutions that I wish to avoid
- Anything that involves me making assumptions based on the fact that the frame has been black for some amount of time. Sure, I could run the video for n seconds and throw up a warning if all the frames I’ve checked up to that point were completely black. If the n is too low, I risk false positives. If the n is too high, the delay between video playback and ‘whoops DRM’ warning could be too long.
- Maintaining a list of known sites that I know utilize DRM
Advertisement
Answer
You can simply look at the HTMLMediaElement’s mediaKeys
property, if it is set, the video is DRM protected:
const isDRMProtected = (elem) => elem.mediaKeys instanceof MediaKeys;