Retour au Blog
Plateforme

Widevine dans les navigateurs automatisés : pourquoi EME échoue

Pourquoi requestMediaKeySystemAccess renvoie NotSupportedError dans Chrome lancé par Playwright, comment fonctionnent les licences Widevine, et un script de vérification.

BotBrowser Team

Documentation

Vous voulez la documentation structurée pour Plateforme ?

Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.

BotBrowser peut garder le rapport de capacités Widevine cohérent avec le profil et appliquer une politique de défi de licence par contexte lorsque votre build dispose de l'autorisation requise et d'un module de déchiffrement de contenu. Il ne peut pas modifier la décision d'un serveur de licences ni rendre un contenu protégé lisible à lui seul.

TL;DR

Un navigateur ne lit une vidéo protégée par Widevine que si l'API Encrypted Media Extensions peut atteindre un module de déchiffrement de contenu (CDM). Lors d'une mesure sur Google Chrome 155 piloté par Playwright 1.58.2, le lancement par défaut a renvoyé NotSupportedError pour chaque configuration de com.widevine.alpha, en mode headless comme avec fenêtre. Retirer un seul argument par défaut, --disable-component-update, a suffi pour que le même Chrome signale la prise en charge. Vérifiez la disponibilité avec un court script avant de déboguer un serveur de licences, et lisez chaque résultat comme une information sur le navigateur, pas sur le fournisseur du contenu.

Sommaire

Ce que font le CDM et la demande de licence

Une page appelle requestMediaKeySystemAccess, le module de déchiffrement du navigateur crée une demande de licence pour le serveur de licences, et un rejet à la première étape signifie que le serveur n'a jamais été contacté

La diffusion protégée sépare trois tâches. Le lecteur récupère des segments chiffrés. Un serveur de licences décide si cet utilisateur et cet appareil peuvent les déchiffrer. Un module de déchiffrement de contenu, exécuté à l'intérieur du navigateur, détient les clés et effectue le déchiffrement pour que la page ne les voie jamais. La spécification Encrypted Media Extensions (EME) est l'interface entre la page et ce module. La page demande un système de clés par son nom, par exemple com.widevine.alpha, et décrit le média qu'elle veut lire. Le navigateur répond par un objet MediaKeySystemAccess ou rejette la demande.

Lorsque la réponse est positive, la page crée des MediaKeys, ouvre une MediaKeySession et transmet au module les données d'initialisation du flux chiffré. Le module répond par un événement message dont le type est license-request. La page envoie ce message opaque à son serveur de licences, reçoit une réponse et la renvoie au module avec session.update(). Si le serveur a approuvé la demande, la session signale les clés utilisables par keystatuseschange et la lecture se poursuit. La page n'interprète jamais le message : c'est une conversation entre le module et le serveur de licences, relayée par le code de l'application.

Deux détails comptent pour le diagnostic. D'abord, la demande de licence n'existe qu'une fois que requestMediaKeySystemAccess a réussi, donc un rejet à cette première étape signifie que le serveur de licences n'a jamais été contacté. Ensuite, la demande est liée au module et à l'appareil : un serveur ne peut décider qu'à partir de ce que le module dit de lui-même, si bien que le même site peut répondre différemment à deux navigateurs qui ont chacun un module installé.

Widevine définit aussi des niveaux de sécurité. Le niveau 3 garde le déchiffrement en logiciel. Les niveaux 2 et 1 s'appuient sur du matériel de confiance à des degrés divers. Dans EME, cela apparaît sous la forme d'une chaîne de robustesse dans les capacités demandées, par exemple SW_SECURE_CRYPTO, SW_SECURE_DECODE ou HW_SECURE_ALL. Un site choisit le niveau le plus fort dont il a besoin pour une résolution donnée ; un navigateur qui ne prend en charge que les niveaux logiciels peut recevoir une résolution plus basse ou être refusé pour du contenu premium. C'est une décision produit du fournisseur de contenu, et vous la voyez comme un résultat de robustesse dans le script ci-dessous.

Un résultat de robustesse décrit ce que le module accepte de négocier dans cet environnement. Ce n'est pas une promesse qu'un titre précis sera lu. Les fournisseurs appliquent leurs propres règles par titre, région, compte et classe d'appareil, et le script ne peut pas voir ces règles. Dans l'autre sens, un niveau matériel non pris en charge n'est pas un défaut de votre configuration. De nombreux environnements de bureau n'ont tout simplement pas de chemin sécurisé matériel, et un fournisseur qui sert du contenu ordinaire en niveaux logiciels fonctionnera sur eux.

Pourquoi un lancement automatisé par défaut ne signale aucune prise en charge

Le module n'est pas qu'un fichier dans le dossier du navigateur : dans cette mesure, sa disponibilité dépendait de l'outil de mise à jour des composants. Les frameworks d'automatisation démarrent généralement le navigateur avec des valeurs par défaut destinées à rendre les exécutions silencieuses et reproductibles. La liste d'arguments par défaut de Playwright pour Chromium inclut --disable-component-update, qui demande au navigateur de ne pas exécuter son outil de mise à jour des composants. C'est une valeur raisonnable pour un exécuteur de tests qui ne veut pas d'activité réseau en arrière-plan. Elle a aussi un effet secondaire facile à manquer : dans cette mesure, un profil neuf lancé ainsi a signalé l'absence de prise en charge de Widevine.

Vérifier le binaire avant les arguments

Les arguments de lancement ne comptent que si le binaire peut porter un module. Les builds open source de Chromium n'embarquent normalement pas le module Widevine ; Google Chrome, si. Une suite qui lance un build Chromium intégré signalera probablement l'absence de prise en charge quels que soient les arguments (cette mesure ne couvre que Chrome installé), et la correction consiste à lancer un Chrome installé. Playwright l'expose avec l'option channel, et d'autres pilotes acceptent un chemin d'exécutable ou un réglage équivalent. Affichez le nom du produit et sa version au début de l'exécution pour que le binaire ne soit jamais une hypothèse implicite.

Un profil temporaire ajoute un facteur de plus. Il repart de zéro à chaque exécution, donc tout ce qui dépend d'un enregistrement au premier démarrage se répète à chaque fois, et un résultat obtenu avec un profil de bureau ancien ne dit rien d'un conteneur de CI qui crée un nouveau dossier par tâche. Comparez ce qui est comparable : profil neuf contre profil neuf, ou profil persistant contre profil persistant.

La mesure

La mesure ci-dessous isole cet argument. Elle a été faite le 2026-10-14 avec Google Chrome 155.0.8059.80 sur macOS, piloté par Playwright 1.58.2, en chargeant une page HTTPS ordinaire et en appelant navigator.requestMediaKeySystemAccess avec une seule capacité H.264. Chaque ligne correspond à un lancement distinct avec un nouveau profil temporaire.

LancementModeRésultat pour com.widevine.alpha
Arguments par défaut de PlaywrightheadlessNotSupportedError pour chaque configuration
Arguments par défaut de Playwrightavec fenêtreNotSupportedError pour chaque configuration
--disable-component-update retiréheadlesspris en charge : défaut, SW_SECURE_CRYPTO, SW_SECURE_DECODE ; HW_SECURE_ALL non pris en charge
--disable-component-update retiréavec fenêtreidentique au mode headless
Chrome ordinaire, profil neuf, sans frameworkavec fenêtrepris en charge : défaut, SW_SECURE_CRYPTO, SW_SECURE_DECODE ; HW_SECURE_ALL non pris en charge

Deux observations en découlent. Le résultat ne dépendait pas du mode headless ou avec fenêtre dans cette mesure, donc le conseil qui accuse le mode headless de l'absence de DRM est incomplet, au moins pour cette version de Chrome. Et le niveau matériel a été signalé comme non pris en charge sur cette machine même quand les niveaux logiciels fonctionnaient, ce qui est la forme attendue pour un navigateur de bureau sans chemin sécurisé matériel ; ne le lisez pas comme un échec de la configuration. Pour voir comment les lancements headless et headed se rapportent au même profil, consultez Cohérence des profils entre navigateurs headless et headed, qui explique comment garder les deux modes alignés.

La mesure montre un effet, pas la cause interne. Le système de composants est celui de Chrome, et une autre version ou un autre système d'exploitation peut se comporter autrement. Ce sur quoi vous pouvez compter, c'est la méthode : changer un argument de lancement, exécuter le même script et comparer.

Reproduire la comparaison dans votre suite

Avec Playwright, l'argument se retire par ignoreDefaultArgs. Gardez tout le reste identique entre les deux exécutions, y compris l'origine de la page et le type de profil, pour que l'argument soit la seule différence.

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();
}

Réactiver l'outil de mise à jour des composants a un coût. Le navigateur peut contacter des serveurs de mise à jour pendant l'exécution, ce qui pose problème dans un réseau de CI hors ligne ou dans une suite qui vérifie les requêtes sortantes. Cela peut aussi rendre les exécutions moins reproductibles si la version d'un composant change entre deux exécutions. Décidez si votre suite a vraiment besoin d'une lecture DRM réelle. Si seuls quelques parcours en ont besoin, activez-le pour ces projets, laissez les valeurs silencieuses pour les autres, et notez le build du navigateur à côté de chaque résultat afin qu'une différence entre exécutions ait une explication visible.

Limites de cette mesure

La mesure couvre un build de navigateur, un système d'exploitation et une version du framework. Elle a testé le système de clés com.widevine.alpha et une capacité H.264. Elle n'a pas lu de contenu, n'a pas contacté de serveur de licences et n'a pas testé Puppeteer, Selenium ni d'autres pilotes, qui peuvent démarrer le navigateur avec d'autres valeurs par défaut. Elle n'a pas non plus testé BotBrowser ; ce que cet article dit de BotBrowser vient de sa documentation publique, liée plus bas, et doit être vérifié avec le build et l'autorisation que vous utilisez réellement. Pour piloter un profil de navigateur avec Selenium plutôt qu'avec Playwright, consultez Intégrer un profil de navigateur avec Selenium, qui explique à qui appartient la session et comment elle est lancée.

Les mises à jour du navigateur changent ces comportements. Chrome peut modifier la façon dont le module est enregistré ou les arguments qui l'affectent, et Playwright peut modifier sa liste d'arguments par défaut. Relancez le script après la mise à jour de l'un ou de l'autre, et conservez le script et sa sortie avec vos notes de test pour qu'un changement apparaisse comme une différence et non comme une surprise.

Un script de vérification et comment le lire

Le script suivant demande la configuration par défaut et trois niveaux de robustesse, plus une session de licence persistante, puis affiche le résultat de chacun. Il n'utilise que des appels EME standard, fonctionne sur toute page servie en HTTPS et ne contacte aucun serveur de licences. Exécutez-le dans votre propre contexte d'automatisation avant d'accuser un site 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);

Résultat attendu sur un navigateur avec un module enregistré : la ligne default et au moins un niveau logiciel sont signalés comme pris en charge. Sur un navigateur sans module, chaque ligne renvoie NotSupportedError. La mesure ci-dessus n'a pas testé les sessions de licence persistantes ; si la ligne persistent échoue alors que les autres passent, comparez avec un lancement ordinaire du même build avant de conclure. Gardez fixes les entrées du script d'une exécution à l'autre : même origine de page, même chaîne de codec, même liste de robustesse. Si vous changez deux choses à la fois, un résultat différent a deux causes possibles et vous ne saurez pas laquelle a compté.

Traitez le résultat comme un court arbre de décision.

  • Chaque ligne est NotSupportedError. Le navigateur n'expose pas le système de clés pour ces capacités. Vérifiez comment le navigateur a été lancé et quels arguments ont été passés. Comparez avec un lancement ordinaire du même build et un profil neuf. Si le lancement ordinaire fonctionne et pas l'automatisé, la différence est dans les arguments ou le profil, pas dans la page.
  • TypeError ou SecurityError. TypeError signifie que la méthode est absente parce que la page n'est pas dans un contexte sécurisé ; SecurityError, qu'une politique d'autorisations bloque l'API. Servez la page en HTTPS ou depuis localhost et vérifiez l'attribut allow du cadre qui l'intègre.
  • La ligne par défaut passe et une ligne de robustesse supérieure échoue. Le module est présent mais seulement à un niveau inférieur. Un fournisseur qui exige un niveau supérieur refusera ou dégradera ; c'est une décision de politique de son côté. Notez le niveau le plus élevé pris en charge par votre environnement et comparez-le à la documentation du fournisseur.
  • La demande passe mais l'appel de licence échoue. Le navigateur a fait sa part : le message license-request a été généré, et l'échec vient de ce que le serveur en a fait. Une erreur HTTP, un compte refusé, une restriction régionale ou un droit expiré sont des conditions côté fournisseur. Consignez le statut HTTP et le code d'erreur du fournisseur, pas le message de licence opaque lui-même, qui n'a aucune valeur de diagnostic pour une personne.
  • La lecture démarre puis s'arrête. Regardez keystatuseschange sur la session. Les clés peuvent passer à expired, output-restricted ou internal-error. Les restrictions de sortie concernent en particulier le chemin d'affichage, par exemple une exigence de sortie protégée qu'un écran virtuel ne satisfait pas, ce qui est encore une affirmation sur l'environnement.

Une liste de contrôle pour une tâche de CI

  1. Fixez le build du navigateur et la version du framework, et affichez les deux au début de la tâche.
  2. Exécutez d'abord le script et échouez vite avec un message clair quand la ligne par défaut n'est pas prise en charge, afin qu'un module manquant ne soit jamais signalé comme une panne du site de streaming.
  3. Conservez le tableau du script comme artefact de la tâche, à côté de la version du navigateur.
  4. Séparez les vérifications de disponibilité de celles de lecture. La disponibilité n'a besoin ni de compte ni d'accès réseau à un fournisseur. La lecture nécessite un compte de test autorisé et appartient à une tâche distincte, clairement étiquetée.
  5. Gardez les messages de licence, les jetons et les cookies hors des journaux et des artefacts. Consignez plutôt les codes de statut et les noms d'erreur.
  6. Relancez le script après chaque mise à jour du navigateur ou du framework et traitez un tableau modifié comme un point de revue, pas comme du bruit.

Deux habitudes gardent ce travail reproductible. Notez le build du navigateur et le système d'exploitation à côté de chaque résultat, car le comportement dépend des deux. Et testez avec du contenu que vous avez le droit de lire, en utilisant les ressources de test du fournisseur quand elles existent ; un échec sur un titre de production en dit moins qu'un échec sur un titre conçu pour le diagnostic.

Ce que fait et ne fait pas BotBrowser

La documentation de BotBrowser décrit la prise en charge de Widevine comme une capacité facultative avec des limites précises. L'intégration de Widevine exige une licence ENT Tier2, et la documentation décrit comment le module est fourni pour un build et une autorisation donnés ; consultez ce guide pour votre version, car cet article ne suppose pas comment le module parvient à votre build. Lorsqu'un profil active la capacité, BotBrowser signale des résultats de capacités DRM cohérents avec l'identité de plateforme du profil : un profil Windows signale un comportement adapté à Windows et un profil Android un comportement adapté à Android. Pour juger si un profil se comporte de façon cohérente d'un système d'exploitation à l'autre, consultez Évaluer la cohérence du navigateur avant le déploiement, qui décrit les vérifications à faire avant la mise en production.

Pour les défis de licence, l'option --bot-widevine choisit une politique par contexte : stub est la valeur par défaut adossée au profil, send autorise le défi normal lorsqu'il est pris en charge, block supprime les demandes de licence et no-cdm désactive l'exposition du module pour le contexte. La politique dépend de l'autorisation et du profil et doit être appliquée avant le démarrage de la première page ou du premier worker.

Les quatre valeurs correspondent aux objectifs de test comme suit.

ObjectifPolitiqueCe que la documentation dit qu'elle fait
Garder le rapport de capacités cohérent avec le profilstubLa politique par défaut, adossée au profil, pour les défis de licence
Parcourir le vrai flux de licence avec un compte autorisésendAutorise le défi normal lorsqu'il est pris en charge
Confirmer que la page se rétablit quand la licence échoueblockSupprime les demandes de licence, donc le chemin d'erreur de la page s'exécute
Confirmer que la page gère un navigateur sans DRMno-cdmDésactive l'exposition du module pour le contexte

Lisez cette liste pour ce qu'elle est. Elle permet à une équipe de faire du comportement DRM une propriété délibérée et reproductible d'un contexte de test plutôt qu'un accident des arguments de lancement. Elle ne fait pas apparaître un module manquant, ne change pas ce que décide un serveur de licences et ne donne pas accès à un contenu protégé que le compte n'a pas le droit de lire. Si votre script signale NotSupportedError et que la politique du profil est send, la question suivante reste de savoir si un module est installé et enregistré pour ce build du navigateur.

Cet article porte sur le diagnostic de disponibilité. Il ne décrit pas comment extraire des clés, capturer des flux déchiffrés ou vaincre les protections d'un fournisseur, et la documentation de BotBrowser non plus. La protection des contenus est un accord entre un titulaire de droits et un spectateur ; un environnement de test existe pour confirmer que la configuration d'un utilisateur légitime fonctionne, pas pour retirer la protection.

Conclusion

Les problèmes de lecture Widevine dans les navigateurs automatisés peuvent être des problèmes de disponibilité au premier appel EME, bien avant l'intervention d'un serveur de licences. Dans la mesure présentée ici, un seul argument par défaut qui désactive les mises à jour des composants a fait toute la différence entre l'absence de prise en charge et une prise en charge de niveau logiciel, en mode headless comme avec fenêtre. Exécutez le script, changez une variable de lancement à la fois et lisez chaque résultat comme un fait sur le navigateur. Déboguez ensuite le flux de licence du fournisseur. BotBrowser peut faire de la capacité DRM et du comportement du défi de licence une partie contrôlée d'un profil lorsque votre build et votre autorisation incluent la prise en charge de Widevine ; il ne remplace ni l'autorisation ni la décision d'un serveur de licences.

Lecture suivante

Poursuivez avec la configuration de Widevine DRM dans BotBrowser pour le côté configuration, la capacité DRM comme signal d'empreinte pour comprendre pourquoi les rapports de capacités doivent rester cohérents, et les signaux MIME et de codecs pour le volet codecs des vérifications de capacités multimédias.

Sources publiques

#Widevine#Drm#Encrypted Media Extensions#Playwright#Automatisation Du Navigateur

Faites passer BotBrowser de la recherche à la production

Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.