EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере

в 8:58, , рубрики: Golasch, javascript, Блог компании JUG.ru Group, обработка видео, потоковое видео, Работа с видео, Разработка веб-сайтов

Что означают все эти аббревиатуры? Что нужно, чтобы разработать open source-плеер для просмотра видео с Amazon, Sky и других платформ и смотреть видео от любого провайдера? О том, как происходит процесс потоковой передачи видео, Себастьян Голаш (Sebastian Golasch) рассказал на конференции HolyJS 2018 Piter. Под катом — видео и перевод его доклада.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 1

В данный момент Себастьян (Sebastian Golasch) занимает должность разработчика в Deutsche Telekom. Достаточно долго он работал с Java и PHP, а затем переключился на JS, Python и Rust. Последние семь лет трудится над фирменной платформой умного дома Qivicon.

Немного об истории потокового видео

Сначала давайте обратимся к истории веба, как мы пришли от QuickTime к Netflix за 25 лет. Все началось в 90-х, когда Apple изобрела QuickTime. Его использование в интернете началось в 1993-1994. В то время проигрыватель мог воспроизводить видео с разрешением 156×116 точек и частотой 10 FPS, без аппаратного ускорения (с использованием лишь ресурсов процессора). Такой формат был ориентирован на dial-up соединение 9600 бод – это 9600 бит в секунду, включая служебную информацию.

Это было время браузера Netscape. Видео в браузере выглядело не слишком хорошо, ведь оно не было нативным для веба. Для проигрывания использовалось внешнее программное обеспечение (тот же QuickTime) со своим интерфейсом, которое визуализировалось в браузере при помощи тега embed.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 2

Ситуация стала немного лучше, когда Macromedia выпустила Shockwave Player (после поглощения Macromedia компанией Adobe он стал называться Adobe Flash Player). Первая версия Shockwave Player была выпущена в 1997 году, но воспроизведение видео в нем появилось лишь в 2002 году.

Там использовалсы кодек Sorenson Spark a.k.a. H.263. Он был оптимизирован для небольших разрешений и маленького размера файла. Что это значит? Например, видео продолжительностью 43 секунды, которое использовалось для тестирования Shockwave Player, весило всего 560 Кбайт. Конечно, фильм в таком качестве смотреть было бы не очень приятно, но сама технология для того времени была интересной. Однако, как и в случае с QuickTime, для работы Shockwave Player в браузере требовалась установка дополнительного ПО. У этого плеера было много проблем с безопасностью, но самое главное — это то, что видео все еще было надстройкой над браузером.

В 2007 году Microsoft выпустила Silverlight, чем-то напоминающий Flash. Мы не будем копать глубоко, но у всех этих решений было кое-что общее — «черный ящик». Все проигрыватели работали как надстройка над браузером, и вы понятия не имели, что происходит внутри.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 3

Элемент <Video/>

В 2007 году компания Opera предложила использовать тег <Video/>, то есть сделать нативное видео в браузере. Мы используем его и сегодня. Это легко и удобно, и любое видео можно не только просмотреть, но и скачать. И если даже мы не хотим позволять скачивание видео, мы не можем запретить его загрузку в бразуер. Максимум — это сделать так, чтобы скачать видео было сложнее.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 4

Тег <Video/> — полная противоположность «черному ящику», и просмотреть исходный код очень просто.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 5

DRM

Однако вы не можете просто так кликнуть правой кнопкой мыши по видео на Netflix и выбрать пункт «Сохранить как». Причина этого — в DRM (Digital Restrictions Management, управление цифровыми ограничениями). Это не одна технология и не единое приложение, которое выполняет какую-либо задачу. Это общий термин для обозначения таких понятий, как:

  • Аутентификация и пользовательское шифрование
  • Шифрование, которое зависит от контента
  • Определение прав и применение ограничений
  • Отзыв и обновление
  • Контроль вывода и защита ссылок
  • Экспертиза и отслеживание нарушителей
  • Управление ключами и лицензиями

Чтобы понять, что из себя представляет DRM, нам нужно изучить его экосистему, то есть разобраться, какие компании вовлечены. Это:

  • Владельцы контента — находятся на вершине экосистемы. Например, Disney, MGM или FIFA. Эти компании производят контент, и у них есть права на него.
  • DRM Cores — это компании, которые предоставляют технологию DRM (например, Google, Apple, Microsoft и пр.) В настоящее время существует около 7–8 технологий DRM от разных компаний.
  • Поставщики услуг — разрабатывают серверное ПО, которое шифрует видео.
  • Браузеры, которые фактически являются проигрывателями.
  • Поставщики контента — это компании типа Netflix, Amazon, Sky и пр. Как правило, они не владеют правами на контент, они лицензируют и распространяют его.
  • Продавцы чипов/устройств — тоже вовлечены в экосистему, ведь DRM — это не только софтверная технология. Некоторые компании (преимущественно китайские) разрабатывают чипы, которые кодируют и декодируют видео.

Вы никогда не задумывались над тем, почему при просмотре видео на Netflix в браузере у него не очень большое разрешение (SD), но если посмотреть то же самое видео на Apple TV или на Android TV Box, тот же контент воспроизводится в Full HD или в 4K? За это тоже отвечает DRM. Дело в том, что производители всегда боятся пиратов, ворующих контент. Поэтому чем менее защищена среда, в которой выполняется декодирование видео, тем в более плохом качестве оно показывается пользователю. Например, если декодирование выполняется программно (например, в Chrome или Firefox), видео показывается в самом плохом качестве. В среде, где задействованы аппаратные возможности для декодирования (например, если Android использует GPU), возможностей нелегального копирования контента меньше, и тут качество воспроизведения выше. Наконец, самой защищенной средой считается полностью аппаратная (Apple TV или Android TV Box), где и декодирование, и воспроизведение выполняются без задействования программной части.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 6

Но если говорить о браузерах, то в них декодирование почти всегда выполняется программными средствами. Разные браузеры используют разные системы для DRM. Chrome и Firefox используют Widevine. Эта компания принадлежит Google и лицензирует их DRM-приложения. Таким образом, для декодирования Firefoх скачивает DRM-библиотеку у Google. В браузере можно увидеть, откуда именно идет загрузка.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 7

Apple использует собственную систему FairPlay, которая была создана еще тогда, когда компания представила первые iPhone и iPad. Microsoft также использует свою разработку под названием PlayReady, которая встроена прямо в Windows. В остальных случаях чаще всего используется Widevine. Эта система существует и как приложение, и в виде аппаратного решения — чипов, которые декодируют видео.

CDM

Аббревиатура CDM расшифровывается как Content Decryption Module (модуль декодирования контента). Это какая-то часть программного или аппаратного обеспечения, которая может работать несколькими способами:

  • Дешифровать видео, после чего оно визуализируется в браузере при помощи тега <Video/>.
  • Дешифровать и декодировать видео, после чего передавать необработанные кадры видео для воспроизведения браузеру.
  • Дешифровать и декодировать видео, после чего передавать необработанные кадры видео для воспроизведения при помощи GPU.

Несмотря на поддержку GPU, чаще всего используется второй вариант (по крайней мере если речь идет о Chrome и Firefox).

Cлои декодирования и дешифрования в браузере

Итак, как все это работает вместе? Чтобы это понять, посмотрим на слои декодирования и дешифрования в браузере. Они разделены на:

  • Приложение JavaScript — оно говорит компьютеру, какое видео я собираюсь смотреть.
  • Браузер — проигрыватель, который воспроизводит видеосодержимое.
  • Модуль дешифрования контента.
  • Управление цифровыми правами — все, что касается декодирования видео (я не мог придумать лучшего обозначения, поэтому назвал это именно так).
  • Доверенная исполняющая среда.
  • При этом два первых компонента являются DRM-плеером, модуль дешифрования контента — DRM-клиентом, а два последних компонента — ядром DRM.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 8

То, что происходит, когда вы воспроизводите видео в браузере, показано на картинке ниже. Она, конечно, немного запутанная: тут много стрелок и цветов. Я пройдусь по ней по шагам, используя реальные кейсы, чтобы было понятнее

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 9

В качестве примера будем использовать Netflix. Я написал приложение для дебаггинга.

Начал я с того, с чего, думаю, начал бы каждый из вас: просмотрел запросы, которые делает Netflix, когда я запускаю видео, и увидел огромное количество записей.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 10

Однако, если оставить только те, которые действительно нужны для воспроизведения видео, окажется, что их всего лишь три: manifest, license и первый фрагмент видео.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 11

Плеер Netflix написан на JavaScript и содержит более 76 000 строчек кода, и, конечно, я не смогу разобрать его полностью. Но я хотел бы показать основные части, которые необходимы для воспроизведения защищенного видео.

Мы начнем с шаблона:

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 12

EME

Но прежде чем мы углубимся в функции, нам необходимо познакомиться с еще одной технологией — EME (Encrypted Media Extensions, зашифрованные медиарасширения). Эта технология не выполняет дешифрования и декодирования, это просто API браузера. EME служит интерфейсом для CDM, для KeySystem, для сервера с лицензией и для сервера, на котором хранится контент.

Итак, давайте начнем с getKeySystemConfig.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 13

Стоит иметь в виду, что она зависит от провайдера, поэтому тот config, который я привожу тут, работает для Netflix, но не работает, скажем, для Amazon.

В этом config мы должны сказать бэкенд-системе, какой уровень доверенной исполняющей среды мы можем предложить. Это может быть безопасное аппаратное декодирование или безопасное программное декодирование. То есть мы говорим системе о том, какое аппаратное и программное обеспечение будет использоваться для воспроизведения. И это определит качество контента.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 14

После настройки config посмотрим на создание initial MediaKeySystem.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 15

Тут начинается взаимодействие с модулем дешифрования контента. Необходимо сообщить API, какую DRM-систему и KeySystem мы используем. В нашем случае это Widevine.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 16

Следующий шаг необязателен для всех систем, но обязателен для Netflix. Опять же, его необходимость зависит от провайдера. Нам нужно применить к нашим mediaKeys сертификат сервера. Сертификаты сервера представляют собой обычный текст в файле Cadmium.js от Netflix, который можно легко копировать. И когда мы применяем его к mediaKeys, то все общение между сервером с лицензией и нашим браузером становится безопасным благодаря использованию этого сертификата.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 17

Когда это сделано, мы должны обратиться к оригинальному элементу видео и сказать: «Ок, это система ключей, которую мы хотим использовать, а это тег hello video. Давайте мы вас объединим».

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 18

А вот последняя функция, которая нужна для настройки видеосистемы.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 19

Это DRM-сессия, или MediaKeySession. Это просто данные, которые идут от провайдера к модулю дешифрования, который подписывает ими запросы. Эти данные также представляют собой обычный текст, который спрятан за несколькими функциями в файле плеера Netflix, откуда я его и скопировал.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 20

Когда мы вызываем create.Session в объекте mediaKeys, необходимо сообщить, какое видео мы поддерживаем. В данном случае это mp4. Это возвращает нас к контексту обмена сообщениями с нашей системой CDM. Нам также нужно, чтобы Netflix применил сертификат сервера в base64 в каждой форме, но весь этот config в сессии create снова предоставляется зависимым от DRM-системы.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 21

Последняя функция тут внизу — keySession.generateRequest строит лицензионный запрос в фоне. Или CDM строит лицензионный запрос в фоне. Иными словами, это необработанные бинарные данные, которые мы должны отправить на лицензионный сервер, чтобы взамен получить действующую лицензию.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 22

Тут интерес представляет cenc. Это ISO-стандарт шифрования, определяющий схему защиты для mp4-видео. У WebM это называется по-другому, но функцию выполняет ту же.

handleMessage — это интерфейс EventListener, который мы настраивали. Когда это событие вызывается событием message в keySession, мы знаем, что мы готовы получить лицензию с сервера.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 23

И в этом callback мы лишь получаем запрос на сервер с лицензией, который отдает некоторые бинарные данные (они также могут отличаться в зависимости от провайдера). Эти данные мы используем для обновления текущей сессии, добавляя лицензию. То есть как только мы получили действующую лицензию с сервера, наша CDM знает, что мы можем декодировать и дешифровать видео.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 24

Если применить это к диаграмме ниже, то получим вот что: мы хотим проиграть видео, и JavaScript-приложение говорит: «Привет, браузер! Я хочу проиграть видео!» — затем использует Encrypted Media Extensions и делает запрос к License Functions в Widevine CDM на получение лицензии. Этот запрос затем возвращается обратно в браузер, и мы можем обменять его на действующую лицензию на сервере лицензий, и затем нужно передать эту лицензию обратно к CDM. Этот процесс и был показан на коде выше.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 25

Но обратите внимание, что мы еще не проиграли ни секунды видео, и это все нам нужно сделать, чтобы в будущем иметь возможность воспроизвести какие-нибудь видеоролики.

MSE

И еще одна технология, которую нам нужно исследовать, — это MSE (Media Source Extensions, расширения для источников мультимедиа). Ее можно назвать сводной сестрой EME (Encrypted Media Extensions). Это тоже API браузера, и она не имеет никакого отношения к DRM. Я рассматриваю ее как программный интерфейс к <Video/> Src. С ее помощью можно создавать бинарные потоки в JavaScript и применять фрагменты видео к элементу <Video/>. Таким образом, благодаря ей исходник тега <Video/> становится динамическим.

Итак, мы можем использовать расширения для источников мультимедиа, инстанцировать и сделать обращение к видео, после чего загрузить фрагменты видео по частям и применить их к тегу <Video/>.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 26

Смысл в том, что, когда вы смотрите двухчасовое видео, вы не хотите ждать, пока оно загрузится полностью. Вместо этого вы разрезаете его на небольшие фрагменты размером примерно от 30 секунд до 2 минут и поочередно применяете их к элементу <Video/>.

Как только наш буфер MediaSource готов и прилинкован к элементу <Video/>, мы можем добавить SourceBuffer. Мы снова должны сказать ему, какой формат видео и какие кодеки мы используем, и тогда он будет создан.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 27

Наконец теперь мы можем начать делать fetch из отдельных фрагментов и отправлять их на нашем SourceBuffer методом append к элементу <Video/>, получая динамически созданное видео. Это также можно использовать для других use-кейсов, когда люди могут самостоятельно комбинировать разные элементы видео, создавая собственные ролики, но мне не хотелось бы зарываться в это слишком глубоко.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 28

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

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 29

Manifest

Но есть еще один момент. Как мы узнаем, какие фрагменты нам нужно загрузить, откуда их загружать и когда? И это последняя часть, отсутствующий запрос из манифеста. Когда мы делаем запрос к Netflix за манифестом, ему требуется много данных. Если мы просто хотим проиграть видео, то для нас имеет значение, какую DRM-систему мы используем, какое видео мы хотим просмотреть (Netflix ID, который можно скопировать из URL) и профили. Профили определяют, в каком разрешении мы получаем видео, а также на каком языке мы получаем аудиодорожки, в каком формате (stereo, Dolby Digital и пр.), используем ли мы субтитры и т. д.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 30

MPEG-DASH

Наиболее часто используемым форматом манифеста является MPEG-DASH. Правда, Apple использует иной формат — HSL, который по виду напоминает список файлов в старом плеере Winamp. Но Widevine и Microsoft используют именно MPEG-DASH. В его основе лежит XML, и он определяет все: продолжительность, размер буфера, типы контента, когда какие фрагменты загружаются, фрагменты для разных разрешений, а также адаптивное переключение битрейта. Последнее означает, что в случае если пользователь, например, смотрит видео и при этом скорость загрузки падает, воспроизведение не останавливается, а просто ухудшается качество видео. Это происходит за счет того, что в манифесте определены одни и те же части для разных разрешений, у них одна и та же продолжительность и одни и те же индексы. Поэтому при уменьшении скорости загрузки браузер может просто переключиться на поток с более низким разрешением, не приостанавливая загрузку и не буферизируя данные.

Вот так выглядит манифест для фильма «Стражи Галактики». В нем мы можем увидеть, что при разной скорости загрузки люди получат видео с разным качеством, а также то, что существуют аудиодорожки для людей с нарушениями слуха. В нем также прописано наличие субтитров.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 31

У нас есть продолжительность и указание на время, с которого надо начать воспроизведение. Эта функция используется, например, тогда, когда вы прерываете просмотр и затем снова возвращаетесь к видео, начиная с того места, где остановились.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 32

Тут также снова есть robustness, который говорит: вот этот фрагмент можно проиграть только в том случае, если ваша система соответствует требованиям. В данном случае это декодирование с помощью аппаратных средств — Hardware Secure Codecs.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 33

Для одной и той же части видео можно определить сколько угодно фрагментов с разными разрешениями.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 34

И затем вы получаете URL для загрузки фрагмента, а параметр range показывает диапазон значений в миллисекундах.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 35

Это и есть последняя часть. Вы также иногда получаете манифест из CDN. У некоторых провайдеров есть отдельный сервер для доставки фрагментов, но чаще всего они приходят с той же машины, что и функциональность манифеста. Когда мы скачали манифест, мы знаем, какие фрагменты нам нужно загрузить, мы можем отправить запрос на фрагменты, после чего дешифровать и декодировать из CDM.

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 36

В общем-то, это все. Всего, что было сказано выше, достаточно для того, чтобы разработать open source плеер для просмотра видео с Amazon, Sky и других платформ и смотреть видео почти любого провайдера.

MSL

В Netflix посчитали, что стоит добавить дополнительное шифрование сообщений между браузером и сервером. Они назвали это «слой безопасности сообщений» — Message Security Layer, или MSL. Он ничего не делает с видео напрямую, это просто дополнительный слой шифрования. Одна из причин внедрения MSL в том, что HTTPS недостаточно безопасен. С другой стороны, MSL имеет открытый исходный код, поэтому всегда можно посмотреть, как он работает. Тут я не собираюсь углубляться в эту тему, и, если вам это интересно, вы всегда можете найти информацию о том, зачем Netflix делает MSL, в их блоге. На GitHub есть подробная документация по его внедрению и рабочие реализации на Java.

Также есть реализация на Python, которую мы написали с друзьями. Насколько я знаю, это единственный рабочий open source клиент для Netflix. Он работает с Kodi Media Center. Для визуализации можно использовать VLC Player или любое другое подходящее ПО.

И снова «черный ящик»

Итак, вы увидели, что нам понадобилось, чтобы внедрить все это, и насколько часто я упоминал CDM — «черный ящик», который загружается с сайта Google. Таким образом, мы снова вернули видео в «черный ящик». Прекрасный элемент <Video/> снова спрятан от нас. Мы добавили стороннее программное обеспечение, которое нам помогает, но которое при этом является закрытым и которым мы не можем управлять. Оно может делать много незаметных вещей: трекинг, аналитику, отправку данных…

EME? CDM? DRM? CENC? IDK! Что нужно, чтобы сделать собственный видеоплеер в браузере - 37

Вот что по этому поводу говорил Тим Бернерс Ли: «Таким образом, в целом важно поддерживать EME как относительно безопасную онлайн-среду, в которой можно смотреть фильмы, а также как наиболее удобную и такую, которая делает ее частью взаимосвязанного дискурса человечества».

Но есть и другие мнения относительно этого. В частности, от Electronic Frontiers Foundation, которая до появления DRM была участником W3C. Вот что они говорят: «В 2013 году EFF была разочарована, узнав, что W3C взяла на себя проект стандартизации EME — зашифрованных медиарасширений. По сути, мы говорим об API, единственной функцией которого должно было стать обеспечение для DRM главной роли в экосистеме браузера. Мы будем продолжать бороться за то, чтобы интернет был свободным и открытым. Мы будем продолжать судиться с правительством США, чтобы отменить законы, которые делают DRM таким токсичным, и мы будем продолжать бороться на уровне общемирового законодательства».

Мне сложно сказать, как к этому относиться. С одной стороны, я всегда за открытый и свободный интернет, в браузерах которого нет закрытого кода, который может отправлять запросы неизвестно куда. С другой стороны, нам нужны сервисы наподобие Netflix, которые взимают плату за видео. Возможно, они могли бы создать собственные приложения для воспроизведения, и тогда интернет отказался бы от такого рода контента.

Уже через месяц, 24-25 ноября, в Москве пройдет HolyJS 2018 Moscow, где Себастьян выступит с докладом «The Universal Serial Web»: подробно разберет новый стандарт WebUSB, возможность работать с USB-устройствами из браузера. Всё это с живыми демо-примерами, очень интересно и наглядно.

P.S. С 1-го ноября установится финальная цена на билеты. Всем, кто идет за свои — скидка 50% от стандартной цены.

Автор: Евгений Трифонов

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js