Назад к блогу
Платформа

Widevine в автоматизированных браузерах: почему EME не работает

Почему requestMediaKeySystemAccess возвращает NotSupportedError в Chrome, запущенном через Playwright, как работают запросы лицензий Widevine и скрипт для проверки готовности DRM.

BotBrowser Team

Документация

Нужна структурированная документация по теме Платформа?

Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.

BotBrowser может поддерживать согласованность отчёта о возможностях Widevine с профилем и применять политику лицензионных запросов для каждого контекста, если в вашей сборке есть необходимое разрешение и модуль расшифровки контента. Он не может изменить решение сервера лицензий или сам по себе сделать защищённый контент воспроизводимым.

TL;DR

Браузер воспроизводит видео, защищённое Widevine, только если API расширений для зашифрованных медиа (EME) может обратиться к модулю расшифровки контента (CDM). В измерении на Google Chrome 155 под управлением Playwright 1.58.2 запуск по умолчанию вернул NotSupportedError для каждой конфигурации com.widevine.alpha, как в режиме headless, так и с окном. Удаление одного аргумента по умолчанию, --disable-component-update, привело к тому, что тот же Chrome сообщил о поддержке. Проверяйте доступность коротким скриптом, прежде чем отлаживать сервер лицензий, и читайте каждый результат как сведения о браузере, а не о поставщике контента.

Содержание

Что делают CDM и запрос лицензии

Страница вызывает requestMediaKeySystemAccess, модуль расшифровки в браузере создаёт запрос лицензии для сервера лицензий, а отказ на первом шаге означает, что сервер не вызывался

Защищённая потоковая передача разделяет три задачи. Плеер получает зашифрованные сегменты. Сервер лицензий решает, может ли этот пользователь на этом устройстве их расшифровать. Модуль расшифровки контента, работающий внутри браузера, хранит ключи и выполняет расшифровку, так что страница ключей никогда не видит. Спецификация расширений для зашифрованных медиа (EME) - это интерфейс между страницей и этим модулем. Страница запрашивает систему ключей по имени, например com.widevine.alpha, и описывает медиа, которое хочет воспроизвести. Браузер отвечает объектом MediaKeySystemAccess или отклоняет запрос.

Если ответ положительный, страница создаёт MediaKeys, открывает MediaKeySession и передаёт модулю данные инициализации зашифрованного потока. Модуль отвечает событием message с типом license-request. Страница отправляет это непрозрачное сообщение на свой сервер лицензий, получает ответ и возвращает его модулю через session.update(). Если сервер одобрил запрос, сессия сообщает о пригодных ключах через keystatuseschange, и воспроизведение продолжается. Сама страница сообщение не интерпретирует: это разговор между модулем и сервером лицензий, который ретранслирует код приложения.

При диагностике важны две детали. Во-первых, запрос лицензии существует только после успешного requestMediaKeySystemAccess, поэтому отказ на этом первом шаге означает, что сервер лицензий вообще не вызывался. Во-вторых, запрос привязан к модулю и устройству: сервер может принимать решение только по тому, что модуль сообщает о себе, поэтому один и тот же сайт может ответить по-разному двум браузерам, в которых установлен модуль.

Widevine также определяет уровни безопасности. Уровень 3 выполняет расшифровку программно. Уровни 2 и 1 в разной степени используют доверенное оборудование. В EME это проявляется как строка robustness в запрошенных возможностях, например SW_SECURE_CRYPTO, SW_SECURE_DECODE или HW_SECURE_ALL. Сайт выбирает самый сильный уровень, нужный ему для данного разрешения; браузеру, поддерживающему только программные уровни, могут отдать меньшее разрешение или отказать в премиальном контенте. Это продуктовое решение поставщика контента, и в скрипте ниже вы увидите его как результат robustness.

Результат robustness описывает, о чём модуль готов договариваться в этой среде. Это не обещание, что конкретный фильм будет воспроизведён. Поставщики применяют собственные правила по названию, региону, аккаунту и классу устройства, а скрипт этих правил не видит. В обратную сторону: неподдерживаемый аппаратный уровень не является дефектом вашей настройки. Во многих настольных средах просто нет аппаратно защищённого пути, и поставщик, который отдаёт обычный контент на программных уровнях, там будет работать.

Почему автоматизированный запуск по умолчанию сообщает об отсутствии поддержки

Модуль - это не просто файл в папке браузера: в этом измерении его доступность зависела от обновления компонентов. Фреймворки автоматизации обычно запускают браузер с набором значений по умолчанию, призванных сделать запуски тихими и воспроизводимыми. В список аргументов Playwright по умолчанию для Chromium входит --disable-component-update, который запрещает браузеру запускать обновление компонентов. Для исполнителя тестов, которому не нужна фоновая сетевая активность, это разумное значение. Но у него есть побочный эффект, который легко пропустить: в этом измерении свежий профиль, запущенный так, сообщал об отсутствии поддержки Widevine.

Сначала проверьте бинарный файл, потом аргументы

Аргументы запуска важны, только если бинарный файл вообще может нести модуль. Сборки Chromium с открытым исходным кодом обычно не включают модуль Widevine, а Google Chrome включает. Набор тестов, запускающий встроенную сборку Chromium, скорее всего будет сообщать об отсутствии поддержки при любых аргументах (это измерение охватывало только установленный Chrome), и решение - запускать установленный Chrome. В Playwright для этого есть опция channel, а другие драйверы принимают путь к исполняемому файлу или эквивалентную настройку. Печатайте название продукта и версию в начале запуска, чтобы бинарный файл никогда не был неоговорённым допущением.

Временный профиль добавляет ещё один фактор. Он каждый раз начинается с чистого листа, поэтому всё, что зависит от регистрации при первом запуске, повторяется, а результат с давно используемым настольным профилем ничего не говорит о CI-контейнере, который на каждое задание создаёт новый каталог. Сравнивайте сопоставимое: новый профиль с новым или постоянный с постоянным.

Измерение

Приведённое ниже измерение изолирует этот аргумент. Оно выполнено 2026-10-14 на Google Chrome 155.0.8059.80 под macOS под управлением Playwright 1.58.2: загружалась обычная HTTPS-страница и вызывался navigator.requestMediaKeySystemAccess с одной возможностью H.264. Каждая строка - отдельный запуск с новым временным профилем.

ЗапускРежимРезультат для com.widevine.alpha
Аргументы Playwright по умолчаниюheadlessNotSupportedError для каждой конфигурации
Аргументы Playwright по умолчаниюс окномNotSupportedError для каждой конфигурации
--disable-component-update удалёнheadlessподдерживаются: по умолчанию, SW_SECURE_CRYPTO, SW_SECURE_DECODE; HW_SECURE_ALL не поддерживается
--disable-component-update удалёнс окномто же, что в headless
Обычный Chrome, чистый профиль, без фреймворкас окномподдерживаются: по умолчанию, SW_SECURE_CRYPTO, SW_SECURE_DECODE; HW_SECURE_ALL не поддерживается

Отсюда два наблюдения. В этом измерении результат не зависел от режима headless или с окном, поэтому совет, обвиняющий headless в отсутствии DRM, неполон по меньшей мере для этой версии Chrome. И аппаратный уровень на этой машине был указан как неподдерживаемый, даже когда программные уровни работали; это ожидаемая картина для настольного браузера без аппаратно защищённого пути, и не стоит читать её как сбой настройки. О том, как headless и headed запуски соотносятся с одним и тем же профилем, читайте в статье Согласованность профиля в headless и headed браузере, где описано, как держать оба режима согласованными.

Измерение показывает эффект, а не внутреннюю причину. Система компонентов принадлежит самому Chrome, и другая версия или другая операционная система могут вести себя иначе. Можно опираться на метод: изменить один аргумент запуска, выполнить тот же скрипт и сравнить.

Воспроизведение сравнения в собственном наборе тестов

В Playwright аргумент удаляется через ignoreDefaultArgs. Всё остальное между двумя запусками держите одинаковым, включая источник страницы и тип профиля, чтобы единственным отличием был этот аргумент.

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

Повторное включение обновления компонентов имеет цену. Браузер может обращаться к серверам обновлений во время запуска, что важно в закрытой сети CI или в наборе, проверяющем исходящие запросы. Это также может снизить воспроизводимость, если версия компонента меняется между двумя запусками. Решите, нужно ли вашему набору реальное воспроизведение DRM. Если оно нужно лишь нескольким сценариям, включайте его для этих проектов, оставляя тихие значения по умолчанию для остальных, и записывайте сборку браузера рядом с каждым результатом, чтобы у различий между запусками было видимое объяснение.

Ограничения этого измерения

Измерение охватывает одну сборку браузера, одну операционную систему и одну версию фреймворка. Проверялась система ключей com.widevine.alpha и одна возможность H.264. Контент не воспроизводился, сервер лицензий не вызывался, Puppeteer, Selenium и другие драйверы не проверялись: они могут запускать браузер с другими значениями по умолчанию. Сам BotBrowser тоже не тестировался; утверждения о нём в этой статье взяты из его публичной документации (ссылка ниже) и должны быть проверены на той сборке и с тем разрешением, которые вы реально используете. О том, как управлять браузерным профилем через Selenium, а не Playwright, читайте в статье Интеграция браузерного профиля с Selenium, где описано, кому принадлежит сессия и как она запускается.

Обновления браузера меняют это поведение. Chrome может изменить способ регистрации модуля или набор аргументов, которые на него влияют, а Playwright - свой список аргументов по умолчанию. Повторяйте скрипт после обновления любого из них и храните скрипт и его вывод вместе с заметками о тестах, чтобы изменение выглядело как разница, а не как сюрприз.

Скрипт проверки и как его читать

Следующий скрипт запрашивает конфигурацию по умолчанию и три уровня robustness, а также постоянную сессию лицензии и выводит результат по каждому. Он использует только стандартные вызовы EME, работает на любой странице по HTTPS и не обращается к серверу лицензий. Запустите его в собственном контексте автоматизации, прежде чем винить стриминговый сайт.

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

Ожидаемый результат в браузере с зарегистрированным модулем: строка default и хотя бы один программный уровень сообщают о поддержке. В браузере без модуля каждая строка возвращает NotSupportedError. Измерение выше не проверяло постоянные сессии лицензии; если строка persistent не проходит, а остальные проходят, сравните с обычным запуском той же сборки, прежде чем делать выводы. Держите входные данные скрипта неизменными между запусками: тот же источник страницы, та же строка кодека, тот же список robustness. Если изменить две вещи сразу, у иного результата будет две возможные причины, и вы не узнаете, какая из них сыграла роль.

Рассматривайте результат как короткое дерево решений.

  • Каждая строка - NotSupportedError. Браузер не предоставляет систему ключей для этих возможностей. Проверьте, как запускался браузер и какие аргументы передавались. Сравните с обычным запуском той же сборки и чистым профилем. Если обычный запуск работает, а автоматизированный нет, разница в аргументах запуска или профиле, а не в странице.
  • TypeError или SecurityError. TypeError означает, что метода нет, потому что страница не в безопасном контексте; SecurityError означает, что политика разрешений блокирует API. Отдавайте страницу по HTTPS или с localhost и проверьте атрибут allow встраивающего фрейма.
  • Строка по умолчанию проходит, а строка с более высоким robustness - нет. Модуль есть, но только на более низком уровне. Поставщик, требующий более высокий уровень, откажет или понизит качество; это решение его политики. Запишите самый высокий уровень, который поддерживает ваша среда, и сравните с документацией поставщика.
  • Запрос проходит, но вызов лицензии не удаётся. Браузер свою часть выполнил: сообщение license-request создано, а сбой в том, что сервер с ним сделал. Ошибка HTTP, отклонённый аккаунт, региональное ограничение или истёкшее право - всё это условия на стороне поставщика. Записывайте статус HTTP и код ошибки поставщика, а не само непрозрачное лицензионное сообщение: для человека оно не имеет диагностической ценности.
  • Воспроизведение начинается, а потом останавливается. Посмотрите keystatuseschange в сессии. Ключи могут перейти в expired, output-restricted или internal-error. Ограничения вывода касаются именно тракта отображения, например требования защищённого вывода, которому виртуальный дисплей не удовлетворяет, и это снова утверждение о среде.

Чек-лист для задания CI

  1. Зафиксируйте сборку браузера и версию фреймворка и печатайте обе в начале задания.
  2. Сначала запускайте скрипт и быстро завершайтесь с понятным сообщением, если строка по умолчанию не поддерживается, чтобы отсутствие модуля никогда не выдавалось за сбой стримингового сайта.
  3. Сохраняйте таблицу результатов скрипта как артефакт задания рядом с версией браузера.
  4. Разделяйте проверки доступности и проверки воспроизведения. Доступности не нужны ни аккаунт, ни сетевой доступ к поставщику. Воспроизведению нужен тестовый аккаунт с разрешением, и оно относится к отдельному, чётко названному заданию.
  5. Не пишите лицензионные сообщения, токены и cookie в журналы и артефакты. Записывайте вместо этого коды статуса и имена ошибок.
  6. Повторяйте скрипт после каждого обновления браузера или фреймворка и считайте изменившуюся таблицу поводом для ревью, а не шумом.

Две привычки делают эту работу воспроизводимой. Записывайте сборку браузера и операционную систему рядом с каждым результатом, потому что поведение зависит от обеих. И тестируйте на контенте, который вам разрешено воспроизводить, используя собственные тестовые материалы поставщика, когда они есть: сбой на рабочем фильме говорит меньше, чем сбой на материале, предназначенном для диагностики.

Что BotBrowser делает и чего не делает

Документация BotBrowser описывает поддержку Widevine как необязательную возможность с определёнными ограничениями. Для интеграции Widevine нужна лицензия ENT Tier2, а документация описывает, как модуль предоставляется для конкретной сборки и разрешения; сверяйтесь с этим руководством для своего релиза, потому что статья не предполагает, как модуль попадает в вашу сборку. Когда профиль включает эту возможность, BotBrowser сообщает результаты DRM-возможностей, согласованные с платформенной идентичностью профиля: профиль Windows сообщает поведение, подходящее для Windows, а профиль Android - для Android. О том, как оценить, ведёт ли себя профиль согласованно на разных операционных системах, читайте в статье Оценка кроссплатформенной согласованности браузера до развертывания, где перечислены проверки перед развёртыванием.

Для лицензионных запросов опция --bot-widevine выбирает политику для каждого контекста: stub - значение по умолчанию, опирающееся на профиль, send разрешает обычный запрос, когда он поддерживается, block подавляет запросы лицензии, а no-cdm отключает доступность модуля для контекста. Политика зависит от разрешения и профиля и должна применяться до запуска первой страницы или воркера.

Четыре значения соответствуют целям тестирования так.

ЦельПолитикаЧто, по документации, она делает
Сохранить согласованность отчёта о возможностях с профилемstubПолитика по умолчанию, опирающаяся на профиль, для лицензионных запросов
Пройти реальный лицензионный процесс с аккаунтом, имеющим праваsendРазрешает обычный запрос, когда он поддерживается
Убедиться, что страница восстанавливается при сбое лицензииblockПодавляет запросы лицензии, поэтому выполняется путь ошибки страницы
Убедиться, что страница справляется с браузером без DRMno-cdmОтключает доступность модуля для контекста

Читайте этот список как есть. Он позволяет команде сделать поведение DRM осознанным, воспроизводимым свойством тестового контекста, а не случайным следствием аргументов запуска. Он не заставляет появиться отсутствующий модуль, не меняет решений сервера лицензий и не даёт доступа к защищённому контенту, который аккаунту воспроизводить не разрешено. Если скрипт сообщает NotSupportedError, а политика профиля - send, следующий вопрос по-прежнему в том, установлен ли и зарегистрирован ли модуль для этой сборки браузера.

Эта статья о диагностике доступности. В ней нет описания того, как извлекать ключи, перехватывать расшифрованные потоки или преодолевать защиту поставщика, и в документации BotBrowser этого тоже нет. Защита контента - это договорённость между правообладателем и зрителем; тестовая среда существует, чтобы убедиться, что настройка законного пользователя работает, а не чтобы снять защиту.

Заключение

Проблемы воспроизведения Widevine в автоматизированных браузерах могут оказаться вопросом «есть или нет» на первом вызове EME, задолго до того, как в дело вступает сервер лицензий. В приведённом измерении один аргумент по умолчанию, отключающий обновление компонентов, составлял всю разницу между отсутствием поддержки и поддержкой на программном уровне, и в режиме headless, и с окном. Запустите скрипт, меняйте по одной переменной запуска и читайте каждый результат как факт о браузере. Только потом отлаживайте лицензионный процесс поставщика. BotBrowser может сделать DRM-возможности и поведение лицензионного запроса управляемой частью профиля, когда ваша сборка и разрешение включают поддержку Widevine; он не заменяет ни разрешение, ни решение сервера лицензий.

Продолжение

Дальше читайте: настройка Widevine DRM в BotBrowser - сторона конфигурации; DRM-возможности как сигнал отпечатка - почему отчёты о возможностях должны оставаться согласованными; сигналы MIME и кодеков - часть про кодеки в проверках медиавозможностей.

Открытые источники

#Widevine#Drm#Encrypted Media Extensions#Playwright#Автоматизация Браузера

Переведите BotBrowser из исследований в продакшн

Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.