Какие форматы изображений и видео декодирует браузер, выдаёт движок, версию и даже GPU. Чем различаются Accept, canPlayType и decodingInfo.
Браузеры по-прежнему расходятся в том, какие форматы изображений и видео они умеют декодировать. Самый наглядный пример — JPEG XL: Safari декодирует его начиная с Safari 17, а Chromium и Firefox то приближались к его поддержке, то снова откладывали её. Всякий раз, когда два движка по-разному отвечают на вопрос «что ты умеешь декодировать?», ответ начинает выдавать сам браузер. Эта статья посвящена именно этой поверхности фингерпринтинга — возможностям декодирования изображений и медиа. Их можно считать тремя способами, и один из них добирается до вашего железа.
Ключевые выводы
- Поддержка форматов считывается на трёх уровнях: HTTP-заголовок
Accept(без JavaScript), запросы возможностей вродеcanPlayType()иisTypeSupported(), а такжеMediaCapabilities.decodingInfo(), который добавляетsmoothиpowerEfficient. - Сама по себе она мало что даёт. Поддержка так тесно связана с семейством и версией браузера, что в основном повторяет User-Agent. Не ждите, что по ней выделят конкретного человека.
- Польза у неё двоякая: проверка на противоречие заявленному UA и биты аппаратного декодирования, которых в UA нет.
- Самое интересное поле —
powerEfficient. Оно показывает, декодируется ли кодек аппаратно, а это отражает GPU, драйвер и ОС — без WebGL и без запроса разрешения. - Меняется она по другому графику. Поддержка форматов меняется при обновлении браузера, ОС и драйвера, а не по расписанию изменений canvas или шрифтов.
Способ 1: заголовок Accept
Первое считывание происходит до запуска любого скрипта. Запрос страницы или изображения несёт заголовок Accept со списком форматов изображений, которые браузер примет. Свежий Chromium отправляет примерно image/avif,image/webp,image/apng,image/svg+xml,image/*, другие движки — более короткие или иначе упорядоченные списки. Именно это сервер видит уже в первом запросе, а статья про фингерпринтинг только по HTTP описывает, как весь набор заголовков читается без JavaScript. Список форматов быстро выдаёт движок и примерную версию, и ничто из того, что вы ставите для блокировки скриптов, его не затрагивает.
Причина изменений со временем проста: браузер добавляет токен, когда выпускает декодер. Справочник MDN по типам файлов изображений перечисляет форматы и их MIME-типы, а страницы Can I Use для AVIF и для JPEG XL показывают, насколько неравномерно распространилась поддержка. AVIF сегодня почти универсален; JPEG XL всё ещё разделяет браузеры, и именно из-за этого расхождения он служит различающим признаком.
Способ 2: запросы возможностей в JavaScript
Скрипт может спросить напрямую, а не выводить ответ из заголовков. HTMLMediaElement.canPlayType() принимает MIME-тип с необязательной строкой кодека и возвращает "", "maybe" или "probably". MediaSource.isTypeSupported() делает то же для потоковых конвейеров, а изображения можно проверять, загружая крошечный образец каждого формата через data URI и глядя, декодируется ли он.
const probes = [
'video/mp4; codecs="avc1.42E01E"',
'video/mp4; codecs="hev1.1.6.L93.B0"',
'video/webm; codecs="vp9"',
'video/mp4; codecs="av01.0.05M.08"',
'audio/ogg; codecs="opus"',
];
const v = document.createElement('video');
const vector = probes.map((p) => v.canPlayType(p) || 'no').join('|');
Получается короткая строка, набор значений в которой зависит от движка, версии и операционной системы: проприетарные кодеки вроде HEVC часто зависят от медиафреймворка платформы, а не только от браузера. Заметьте, что различие между "maybe" и "probably" само является различием между реализациями, а не просто «да» и «нет».
Способ 3: MediaCapabilities и аппаратные биты
Третье считывание — то, ради которого эта статья написана отдельно. navigator.mediaCapabilities.decodingInfo() принимает полную конфигурацию — кодек, разрешение, битрейт, частоту кадров — и возвращает три булевых значения: supported, smooth и powerEfficient.
const info = await navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/mp4; codecs="hev1.1.6.L93.B0"',
width: 3840, height: 2160, bitrate: 20_000_000, framerate: 60,
},
});
// { supported: true, smooth: true, powerEfficient: true }
powerEfficient по сути сообщает, есть ли аппаратное декодирование. Машина, чей GPU декодирует 4K HEVC или AV1 аппаратно, отвечает иначе, чем та, что откатывается на программное декодирование, а ответ зависит от поколения GPU, драйвера и медиастека ОС. Перебор сетки кодеков, размеров и частот кадров даёт небольшой вектор с аппаратным оттенком — информацию, которой нет в User-Agent. Это аналог GPU-сигналов из статьи про фингерпринтинг WebGPU, только на уровне медиа-API, и материал для перекрёстных проверок, описанных в статье о несоответствии отпечатка и железа, — причём без разрешений и без canvas.
Сколько здесь энтропии, если честно
Сама по себе немного. Два человека с одной версией Chrome на одной ОС сообщают почти одинаковые списки Accept и векторы canPlayType, поэтому программная половина этой поверхности в основном повторяет семейство и версию браузера. Скажем прямо: поддержка форматов — не сильный самостоятельный идентификатор.
Полезной она становится двумя путями:
- Проверки на противоречие. Заявленный Safari, не декодирующий HEVC, или заявленный свежий Chrome без AV1 не сходятся. Системы детекции используют это вместе с обнаружением headless-браузеров, где отсутствие кодеков — классический признак, и в более широких проверках согласованности.
- Аппаратные биты.
powerEfficientиsmoothна сетке проб разделяют машины с одной сборкой браузера, но разными GPU, чего UA не может.
Не стоит путать это с соседней темой: фингерпринтинг WebRTC читает кодеки, которые браузер объявляет для звонков в реальном времени, а эта статья — о возможностях декодирования сохранённых и потоковых медиа и изображений.
Стабильность: другая кривая затухания
Сигналы canvas и шрифтов меняются, когда вы ставите шрифты или обновляете графический стек. Поддержка форматов меняется, когда браузер выпускает или убирает декодер, когда ОС обновляет медиафреймворк или когда обновление драйвера включает либо выключает аппаратное декодирование. Так у сигнала появляется ступенчатый характер, привязанный к циклам обновлений. Для того, кому нужен стабильный идентификатор, это палка о двух концах: между обновлениями сигнал надёжен, а потом резко меняется. Для того, кто защищается от трекинга, обновление действительно меняет эту часть профиля, но поскольку множество пользователей обновляются примерно одновременно, вы скорее переходите в новую большую группу, чем начинаете выделяться.
Что можно сделать
Переключателя для этого нет. Поддержка форматов — функциональная возможность, и её подделка ломает видео и изображения. Практические варианты:
- Используйте распространённый, актуальный браузер, чтобы ваши ответы совпадали с большой толпой, а не с редкой комбинацией.
- Где возможно, выбирайте режимы защиты от фингерпринтинга, ограничивающие то, что раскрывают API медиавозможностей; они жертвуют частью функциональности ради меньшей поверхности.
- С подозрением относитесь к инструментам, подменяющим UA без соответствующего профиля декодирования — именно такое несоответствие этот сигнал и ловит.
Чтобы увидеть, что сообщает ваш браузер и согласуется ли это с другими сигналами, запустите проверку отпечатка.
FAQ
Поддержка кодеков — сильный отпечаток? Нет. Это грубый сигнал: он тесно следует за версией браузера и в основном повторяет User-Agent. Ценность — в проверках на противоречие и битах аппаратного декодирования.
Останавливает ли это блокировка JavaScript?
Только считывания через JavaScript. Заголовок Accept отправляется в первом запросе в любом случае.
Почему powerEfficient раскрывает больше, чем supported?
supported следует за сборкой браузера, а powerEfficient — за вашим GPU, драйвером и ОС.


