Skip to content

Widevine / EME (DRM)

Play DRM-protected video (and match a real Chrome's EME surface) by opting in to the Widevine CDM — fetched at runtime exactly the way a stock Chrome receives it.

Why it's opt-in

Clearcote ships the EME/Widevine plumbing compiled in (enable_widevine) but not Google's proprietary CDM, which Clearcote does not redistribute — and which could not ship in the open-source build. Without a CDM, navigator.requestMediaKeySystemAccess('com.widevine.alpha') rejects, which is both a broken-DRM problem and a coherence tell (a real Chrome resolves it). So you trigger the download — Clearcote never distributes the CDM.

Enable it

Pass widevine. The SDK downloads the CDM once from Google's own component server, verifies its SHA-256, seeds it into the profile, and enables it — then DRM pages play. It works with launch_persistent_context and, since SDK 0.23, with Python's launch(), which now runs on a throwaway profile rather than incognito. In Node use launchPersistentContext (the TypeScript type of launch() doesn't declare widevine yet). Python & Node; the .NET SDK has no Widevine option yet.

python
from clearcote import launch_persistent_context

ctx = launch_persistent_context("./profile-drm", widevine=True)   # fetch + seed + enable the CDM
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://your-drm-site.example")
# navigator.requestMediaKeySystemAccess('com.widevine.alpha') now resolves
javascript
import { launchPersistentContext, fetchWidevine } from "clearcote";

// optional: pre-fetch the CDM ahead of time (cached under ~/.clearcote/WidevineCdm)
await fetchWidevine();

const ctx = await launchPersistentContext("./profile-drm", { widevine: true });

How it works & limits

  • Opt-in, user-triggered — the CDM is downloaded once from Google's component server, SHA-256 verified (a missing hash is refused — it's a native DLL), and cached under ~/.clearcote/WidevineCdm (move it with CLEARCOTE_WIDEVINE_DIR). Pre-fetch with fetch_widevine() / fetchWidevine(). Each launch with widevine also makes one version check to Google's update server, directly from this machine rather than through your proxy.
  • A profile is required — the CDM lives in the profile, so it works with the sync launch() and launch_persistent_context but not with the incognito launches (ephemeral_profile=False, the async launch()). The SDK un-suppresses the component updater (and, on Windows, forces a fast-update scan) so the engine registers the CDM. Works on Windows and Linux.
  • Licensed 154 r32 and newer — the engine takes --widevine-cdm-path, a CDM directory it registers at startup in every profile, Playwright's throwaway ones included, without waiting for the component updater. From SDK 0.41.0 a widevine launch passes it for you, pointing at the CDM it fetched (the SDK checks the engine has the switch first, so older builds keep the profile route above). The widevine option is still taken only by the launches that have a profile. For any other launch on such a build (the incognito ones, serve()), add --widevine-cdm-path=<dir> to args yourself, with the directory fetch_widevine() / fetchWidevine() returns.
  • Docker — the official image turns it on by default for a Windows persona (CC_WIDEVINE=0 to turn it off).
  • Software-secure (L3) playback; hardware-secure (L1) is out of scope.
  • Best-effort — if the CDM can't be fetched or the version check fails (offline, say), the launch proceeds without DRM rather than failing.
DRM support is one more piece of a coherent identity: a real Chrome answers the Widevine query, so a persona that can't is a tell — and the default persona presents as Google Chrome, which ships the CDM on every desktop platform. See How detection works.