Widevine en navegadores automatizados: por qué falla EME
Por qué requestMediaKeySystemAccess devuelve NotSupportedError en Chrome lanzado con Playwright, cómo funcionan las licencias de Widevine y un script para comprobarlo.
BotBrowser Team
Quieres la documentación estructurada de Plataforma?
Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.
BotBrowser puede mantener coherente con el perfil el informe de capacidades de Widevine y aplicar una política de desafío de licencia por contexto cuando tu compilación dispone de la autorización necesaria y de un módulo de descifrado de contenido. No puede cambiar la decisión de un servidor de licencias ni hacer que el contenido protegido se reproduzca por sí solo.
TL;DR
Un navegador solo reproduce vídeo protegido con Widevine cuando la API Encrypted Media Extensions puede llegar a un módulo de descifrado de contenido (CDM). En una medición con Google Chrome 155 controlado por Playwright 1.58.2, el lanzamiento por defecto devolvió NotSupportedError para toda configuración de com.widevine.alpha, tanto en modo headless como con ventana. Al quitar un único argumento por defecto, --disable-component-update, el mismo Chrome pasó a informar compatibilidad. Comprueba la disponibilidad con un script breve antes de depurar un servidor de licencias y lee cada resultado como un dato sobre el navegador, no sobre el proveedor del contenido.
Contenido
- Qué hacen el CDM y la solicitud de licencia
- Por qué un lanzamiento automatizado por defecto no informa compatibilidad
- Un script de comprobación y cómo leerlo
- Qué hace y qué no hace BotBrowser
- Conclusión
Qué hacen el CDM y la solicitud de licencia
La transmisión protegida separa tres tareas. El reproductor obtiene segmentos cifrados. Un servidor de licencias decide si este usuario y este dispositivo pueden descifrarlos. Un módulo de descifrado de contenido, que se ejecuta dentro del navegador, guarda las claves y realiza el descifrado para que la página nunca las vea. La especificación Encrypted Media Extensions (EME) es la interfaz entre la página y ese módulo. La página pide un sistema de claves por su nombre, como com.widevine.alpha, y describe el contenido que quiere reproducir. El navegador responde con un objeto MediaKeySystemAccess o rechaza la solicitud.
Cuando la respuesta es positiva, la página crea MediaKeys, abre una MediaKeySession y entrega al módulo los datos de inicialización del flujo cifrado. El módulo responde con un evento message cuyo tipo es license-request. La página envía ese mensaje opaco a su servidor de licencias, recibe una respuesta y se la devuelve al módulo con session.update(). Si el servidor aprobó la solicitud, la sesión informa de las claves utilizables mediante keystatuseschange y la reproducción continúa. La página nunca interpreta el mensaje; es una conversación entre el módulo y el servidor de licencias que el código de la aplicación se limita a retransmitir.
Dos detalles importan al diagnosticar. Primero, la solicitud de licencia solo existe después de que requestMediaKeySystemAccess tenga éxito, de modo que un rechazo en ese primer paso significa que el servidor de licencias nunca fue contactado. Segundo, la solicitud está ligada al módulo y al dispositivo: un servidor solo puede decidir a partir de lo que el módulo dice de sí mismo, así que el mismo sitio puede responder de forma distinta a dos navegadores que tienen un módulo instalado.
Widevine también define niveles de seguridad. El nivel 3 mantiene el descifrado en software. Los niveles 2 y 1 usan hardware de confianza en distinto grado. En EME esto aparece como una cadena de robustez en las capacidades solicitadas, por ejemplo SW_SECURE_CRYPTO, SW_SECURE_DECODE o HW_SECURE_ALL. Un sitio elige el nivel más fuerte que necesita para una resolución determinada; un navegador que solo admite los niveles de software puede recibir una resolución menor o ser rechazado para contenido premium. Es una decisión de producto del proveedor del contenido y, en el script de más abajo, la verás como un resultado de robustez.
Un resultado de robustez describe lo que el módulo está dispuesto a negociar en este entorno. No es una promesa de que un título concreto se reproducirá. Los proveedores aplican sus propias reglas por título, región, cuenta y clase de dispositivo, y el script no puede ver esas reglas. En sentido contrario, que un nivel de hardware no esté disponible no es un defecto de tu configuración. Muchos entornos de escritorio simplemente no tienen una ruta segura por hardware, y un proveedor que sirve contenido ordinario en niveles de software funcionará allí.
Por qué un lanzamiento automatizado por defecto no informa compatibilidad
El módulo no es solo un archivo en la carpeta del navegador: en esta medición, su disponibilidad dependió del actualizador de componentes. Los marcos de automatización suelen iniciar el navegador con una serie de valores por defecto pensados para que las ejecuciones sean silenciosas y repetibles. La lista de argumentos por defecto de Playwright para Chromium incluye --disable-component-update, que indica al navegador que no ejecute su actualizador de componentes. Es un valor razonable para un ejecutor de pruebas que no quiere actividad de red en segundo plano. Pero tiene un efecto secundario fácil de pasar por alto: en esta medición, un perfil recién creado y lanzado así informó de que no había compatibilidad con Widevine.
Comprueba el binario antes que los argumentos
Los argumentos de lanzamiento solo importan cuando el binario puede llevar un módulo. Las compilaciones de código abierto de Chromium normalmente no incluyen el módulo Widevine; Google Chrome sí. Una suite que lance una compilación de Chromium incluida probablemente informará de que no hay compatibilidad pase los argumentos que pase (esta medición solo cubrió Chrome instalado), y la solución es lanzar una instalación de Chrome. Playwright lo expone con la opción channel, y otros controladores aceptan una ruta al ejecutable o un ajuste equivalente. Imprime el nombre del producto y la versión al empezar la ejecución para que el binario nunca sea una suposición sin declarar.
Un perfil temporal añade otro factor. Empieza limpio en cada ejecución, de modo que todo lo que dependa del registro en el primer arranque se repite cada vez, y un resultado de un perfil de escritorio de larga duración no es evidencia sobre un contenedor de CI que crea un directorio nuevo por trabajo. Compara lo mismo con lo mismo: perfil nuevo contra perfil nuevo, o perfil persistente contra perfil persistente.
La medición
La medición siguiente aísla ese argumento. Se realizó el 2026-10-14 con Google Chrome 155.0.8059.80 en macOS, controlado por Playwright 1.58.2, cargando una página HTTPS corriente y llamando a navigator.requestMediaKeySystemAccess con una única capacidad H.264. Cada fila es un lanzamiento independiente con un perfil temporal nuevo.
| Lanzamiento | Modo | Resultado para com.widevine.alpha |
|---|---|---|
| Argumentos por defecto de Playwright | headless | NotSupportedError en toda configuración |
| Argumentos por defecto de Playwright | con ventana | NotSupportedError en toda configuración |
Sin --disable-component-update | headless | compatibles: por defecto, SW_SECURE_CRYPTO, SW_SECURE_DECODE; HW_SECURE_ALL no compatible |
Sin --disable-component-update | con ventana | igual que headless |
| Chrome normal, perfil nuevo, sin marco | con ventana | compatibles: por defecto, SW_SECURE_CRYPTO, SW_SECURE_DECODE; HW_SECURE_ALL no compatible |
De aquí se derivan dos observaciones. En esta medición el resultado no dependió de headless frente a ventana, así que el consejo que culpa al modo headless de la falta de DRM es incompleto, al menos para esta versión de Chrome. Y el nivel respaldado por hardware se informó como no compatible en esta máquina aunque los niveles de software funcionaran, que es lo esperable en un navegador de escritorio sin ruta segura por hardware; no lo interpretes como un fallo de la configuración. Para ver cómo se relacionan los lanzamientos headless y headed con el mismo perfil, consulta Consistencia de perfiles entre navegadores headless y headed, que explica cómo mantener alineados ambos modos.
La medición muestra un efecto, no la causa interna. El sistema de componentes es propio de Chrome y otra versión u otro sistema operativo pueden comportarse de forma distinta. Lo que sí puedes aprovechar es el procedimiento. Cambia un argumento de lanzamiento, ejecuta el mismo script y compara.
Reproducir la comparación en tu propia suite
Con Playwright, el argumento se quita mediante ignoreDefaultArgs. Mantén todo lo demás idéntico entre las dos ejecuciones, incluidos el origen de la página y el tipo de perfil, para que el argumento sea la única diferencia.
import { chromium } from 'playwright';
const variants = {
playwrightDefaults: {},
componentUpdateEnabled: { ignoreDefaultArgs: ['--disable-component-update'] },
};
for (const [label, options] of Object.entries(variants)) {
const browser = await chromium.launch({ channel: 'chrome', headless: true, ...options });
const page = await browser.newPage();
await page.goto('https://example.com/');
const supported = await page.evaluate(() =>
navigator
.requestMediaKeySystemAccess('com.widevine.alpha', [
{
initDataTypes: ['cenc'],
videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }],
},
])
.then(
() => true,
() => false
)
);
console.log(label, supported);
await browser.close();
}
Reactivar el actualizador de componentes tiene costes. El navegador puede contactar con servidores de actualización durante la ejecución, lo que importa en una red de CI sin salida o en una suite que verifica las peticiones salientes. También puede hacer las ejecuciones menos repetibles si cambia la versión de un componente entre dos ejecuciones. Decide si tu suite necesita realmente reproducción DRM. Si solo la necesitan unos pocos recorridos, actívalo en esos proyectos y deja los valores silenciosos en el resto, y anota la compilación del navegador junto a cada resultado para que una diferencia entre ejecuciones tenga una explicación visible.
Límites de esta medición
La medición cubre una compilación del navegador, un sistema operativo y una versión del marco. Probó el sistema de claves com.widevine.alpha y una capacidad H.264. No reprodujo contenido, no contactó con un servidor de licencias y no probó Puppeteer, Selenium ni otros controladores, que pueden iniciar el navegador con valores por defecto distintos. Tampoco probó BotBrowser; lo que este artículo dice de BotBrowser procede de su documentación pública, enlazada más abajo, y debe verificarse con la compilación y la autorización que uses realmente. Para controlar un perfil de navegador desde Selenium en lugar de Playwright, consulta Integración de perfiles de navegador con Selenium, que explica a quién pertenece la sesión y cómo se lanza.
Las actualizaciones del navegador cambian estos comportamientos. Chrome puede cambiar cómo se registra el módulo o qué argumentos lo afectan, y Playwright puede cambiar su lista de argumentos por defecto. Repite el script después de actualizar cualquiera de los dos y guarda el script y su salida con tus notas de prueba, para que un cambio aparezca como una diferencia y no como una sorpresa.
Un script de comprobación y cómo leerlo
El script siguiente pide la configuración por defecto y tres niveles de robustez, más una sesión de licencia persistente, y muestra el resultado de cada uno. Usa solo llamadas EME estándar, funciona en cualquier página servida por HTTPS y no contacta con ningún servidor de licencias. Ejecútalo en tu propio contexto de automatización antes de culpar a un sitio de streaming.
const configs = {
default: { robustness: undefined, sessionTypes: ['temporary'] },
SW_SECURE_CRYPTO: { robustness: 'SW_SECURE_CRYPTO', sessionTypes: ['temporary'] },
SW_SECURE_DECODE: { robustness: 'SW_SECURE_DECODE', sessionTypes: ['temporary'] },
HW_SECURE_ALL: { robustness: 'HW_SECURE_ALL', sessionTypes: ['temporary'] },
persistent: { robustness: undefined, sessionTypes: ['persistent-license'] },
};
const results = {};
for (const [name, cfg] of Object.entries(configs)) {
const capability = { contentType: 'video/mp4; codecs="avc1.42E01E"' };
if (cfg.robustness) capability.robustness = cfg.robustness;
try {
const access = await navigator.requestMediaKeySystemAccess('com.widevine.alpha', [
{
initDataTypes: ['cenc'],
sessionTypes: cfg.sessionTypes,
videoCapabilities: [capability],
},
]);
results[name] = `supported (${access.getConfiguration().videoCapabilities?.[0]?.robustness || 'default'})`;
} catch (error) {
results[name] = error.name;
}
}
console.table(results);
Resultado esperado en un navegador con módulo registrado: la fila default y al menos un nivel de software figuran como compatibles. En un navegador sin módulo, todas las filas devuelven NotSupportedError. La medición anterior no probó sesiones de licencia persistentes; si la fila persistent falla mientras las demás pasan, compárala con un lanzamiento normal de la misma compilación antes de sacar conclusiones. Mantén fijas las entradas del script entre ejecuciones: el mismo origen de página, la misma cadena de códec, la misma lista de robustez. Si cambias dos cosas a la vez, un resultado distinto tiene dos causas posibles y no sabrás cuál importó.
Trata el resultado como un árbol de decisión breve.
- Todas las filas son
NotSupportedError. El navegador no expone el sistema de claves para estas capacidades. Revisa cómo se lanzó el navegador y qué argumentos se pasaron. Compara con un lanzamiento normal de la misma compilación y un perfil nuevo. Si el lanzamiento normal funciona y el automatizado no, la diferencia está en los argumentos o en el perfil, no en la página. TypeErroroSecurityError.TypeErrorsignifica que el método no existe porque la página no está en un contexto seguro;SecurityError, que una política de permisos bloquea la API. Sirve la página por HTTPS o desdelocalhosty revisa el atributoallowdel marco que la incrusta.- La fila por defecto pasa y una fila de robustez mayor falla. El módulo está presente, pero solo a un nivel inferior. Un proveedor que exija un nivel mayor rechazará o degradará; es una decisión de política suya. Anota el nivel más alto que admite tu entorno y compáralo con la documentación del proveedor.
- La solicitud pasa pero la llamada de licencia falla. El navegador ya ha hecho su parte: el mensaje
license-requestse generó y el fallo está en lo que el servidor hizo con él. Un error HTTP, una cuenta rechazada, una restricción regional o una autorización caducada son condiciones del lado del proveedor. Registra el estado HTTP y el código de error del proveedor, no el mensaje de licencia opaco, que no tiene valor de diagnóstico para una persona. - La reproducción empieza y luego se detiene. Mira
keystatuseschangeen la sesión. Las claves pueden pasar aexpired,output-restrictedointernal-error. Las restricciones de salida, en particular, tienen que ver con la ruta de visualización, por ejemplo un requisito de salida protegida que una pantalla virtual no cumple, lo que de nuevo es una afirmación sobre el entorno.
Lista de comprobación para un trabajo de CI
- Fija la compilación del navegador y la versión del marco, e imprime ambas al empezar el trabajo.
- Ejecuta primero el script y falla pronto con un mensaje claro cuando la fila por defecto no sea compatible, de modo que la falta del módulo nunca se informe como un fallo del sitio de streaming.
- Guarda la tabla del script como artefacto del trabajo junto a la versión del navegador.
- Separa las comprobaciones de disponibilidad de las de reproducción. La disponibilidad no necesita cuenta ni acceso de red a un proveedor. La reproducción necesita una cuenta de prueba autorizada y va en un trabajo distinto con una etiqueta clara.
- Mantén los mensajes de licencia, los tokens y las cookies fuera de los registros y artefactos. Registra códigos de estado y nombres de error en su lugar.
- Repite el script tras cada actualización del navegador o del marco y trata una tabla distinta como un asunto de revisión, no como ruido.
Dos hábitos mantienen este trabajo reproducible. Anota la compilación del navegador y el sistema operativo junto a cada resultado, porque el comportamiento depende de ambos. Y prueba con contenido que tengas derecho a reproducir, usando los recursos de prueba del propio proveedor cuando existan; un fallo con un título de producción dice menos que un fallo con un título pensado para diagnóstico.
Qué hace y qué no hace BotBrowser
La documentación de BotBrowser describe la compatibilidad con Widevine como una capacidad opcional con límites concretos. La integración de Widevine requiere una licencia ENT Tier2, y la documentación describe cómo se proporciona el módulo para una compilación y una autorización concretas; consulta esa guía para tu versión, porque este artículo no presupone cómo llega el módulo a tu compilación. Cuando un perfil activa la capacidad, BotBrowser informa de resultados de capacidad DRM coherentes con la identidad de plataforma del perfil: un perfil de Windows informa de un comportamiento propio de Windows y un perfil de Android, de uno propio de Android. Para juzgar si un perfil se comporta de forma coherente entre sistemas operativos, consulta Evaluar la coherencia del navegador antes del despliegue, que describe las comprobaciones previas al despliegue.
Para los desafíos de licencia, la opción --bot-widevine elige una política por contexto: stub es el valor por defecto respaldado por el perfil, send permite el desafío normal cuando es compatible, block suprime las solicitudes de licencia y no-cdm desactiva la exposición del módulo en el contexto. La política depende de la autorización y del perfil y debe aplicarse antes de que empiece la primera página o el primer worker.
Los cuatro valores se corresponden con objetivos de prueba así.
| Objetivo | Política | Qué dice la documentación que hace |
|---|---|---|
| Mantener el informe de capacidades coherente con el perfil | stub | La política por defecto, respaldada por el perfil, para desafíos de licencia |
| Recorrer el flujo real de licencia con una cuenta autorizada | send | Permite el desafío normal cuando es compatible |
| Confirmar que la página se recupera cuando falla la licencia | block | Suprime las solicitudes de licencia, de modo que se ejecuta la ruta de error de la página |
| Confirmar que la página gestiona un navegador sin DRM | no-cdm | Desactiva la exposición del módulo en el contexto |
Lee esa lista por lo que es. Permite a un equipo convertir el comportamiento DRM en una propiedad deliberada y repetible de un contexto de prueba, en lugar de un accidente de los argumentos de lanzamiento. No hace aparecer un módulo ausente, no cambia lo que decide un servidor de licencias y no da acceso a contenido protegido que la cuenta no tenga derecho a reproducir. Si el script informa NotSupportedError y la política del perfil es send, la siguiente pregunta sigue siendo si hay un módulo instalado y registrado para esa compilación del navegador.
Este artículo trata del diagnóstico de disponibilidad. No describe cómo extraer claves, capturar flujos descifrados ni vencer las protecciones de un proveedor, y la documentación de BotBrowser tampoco. La protección de contenido es un acuerdo entre un titular de derechos y un espectador; un entorno de pruebas existe para confirmar que la configuración de un usuario legítimo funciona, no para quitar la protección.
Conclusión
Los problemas de reproducción de Widevine en navegadores automatizados pueden ser problemas de «está o no está» en la primera llamada EME, mucho antes de que intervenga un servidor de licencias. En la medición de aquí, un único argumento por defecto que desactiva las actualizaciones de componentes marcó toda la diferencia entre no tener compatibilidad y tener compatibilidad a nivel de software, tanto en modo headless como con ventana. Ejecuta el script, cambia una variable de lanzamiento cada vez y lee cada resultado como un dato sobre el navegador. Después depura el flujo de licencia del proveedor. BotBrowser puede convertir la capacidad DRM y el comportamiento del desafío de licencia en una parte controlada de un perfil cuando tu compilación y tu autorización incluyen compatibilidad con Widevine; no sustituye la autorización ni la decisión de un servidor de licencias.
Lectura siguiente
Continúa con la configuración de Widevine DRM en BotBrowser para la parte de configuración, la capacidad DRM como señal de huella para entender por qué los informes de capacidad deben mantenerse coherentes, y las señales de compatibilidad MIME y de códecs para la parte de códecs de las comprobaciones de capacidad multimedia.
Fuentes públicas
Artículos Relacionados
Fingerprinting DRM: qué revelan las consultas EME y Widevine
Plataforma¿Qué es Widevine DRM? Configuración headless y privacidad
PlataformaDetección de funciones de WebAssembly y portabilidad de módulos
PlataformaConsistencia de perfiles entre navegadores headless y headed
PlataformaEvaluar la coherencia del navegador antes del despliegue
Primeros pasosIntegración de perfiles de navegador con Selenium
Lleva BotBrowser de la investigación a producción
Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.