# livedigital > livedigital documentation portal (public build) ## search - [Search the documentation](/search.md) ## docs ### android-sdk - [Отправка аналитики](/docs/android-sdk/analytics.md): В процессе работы SDK отправляет на сервер livedigital анонимные аналитические данные, которые помогают нам находить ошибки и улучшать его работу. - [Обмен дополнительными данными](/docs/android-sdk/appdata.md): В процессе звонка участники могут отправлять друг другу произвольные данные. Для этого каждый участник при подключении к серверу указывает один объект JSONObject, который могут прочитать другие участники. Этот объект может быть изменён в любое время. - [Системные разрешения Android](/docs/android-sdk/call-permissions.md): Ниже приведён полный список разрешений, которые могут потребоваться для различных сценариев работы приложения. - [Настройка качества](/docs/android-sdk/encoding.md): При трансляции видео и аудио SDK получает медиапоток с устройства и кодирует его перед передачей другим участникам звонка. - [Введение](/docs/android-sdk/intro.md): livedigital Android SDK позволяет интегрировать ваше Android-приложение с сервисом livedigital для участия в видеозвонках с мобильного устройства. - [Звонки на телефон](/docs/android-sdk/phone-calls.md): Есть два способа реализации телефонных звонков с помощью livedigital SDK: - [Android SDK Reference](/docs/android-sdk/sdk-reference.md): Актуальная версия ### category - [API](/docs/category/moodhood-api.md): livedigital common API - [Push Registration API](/docs/category/push-registration-api.md): Push Registration API - [Авторизация](/docs/category/авторизация.md) - [Дополнительная информация](/docs/category/дополнительная-информация.md) - [Доступ пользователей](/docs/category/доступ-пользователей.md) ### docs - [Руководство администратора](/docs/docs/admin_manual.md): ``` ### Аттрибуты элемента iFrame:[​](#аттрибуты-элемента-iframe "Direct link to Аттрибуты элемента iFrame:") * src - URL для доступа в комнату, где указывается alias созданной комнаты (см. [Создание комнат](/docs/tutorials/rooms.md)). Также в качестве параметров здесь после символа “?” можно указать имя пользователя (participantName) и его refresh\_token (refreshToken) или access\_token (accessToken) для фоновой [авторизации](/docs/tutorials/auth/auth_intro.md) в комнате. * id - устаревший атрибут для iFrame, оставленный для совместимости с ранними версиями браузеров, значение может быть любым. * frameborder - ширина рамки вокруг фрейма (следует оставить 0 ). * width/height - ширина/высота для отображения фрейма на странице. * disableSupport="1" - отключает тех. поддержку ВКС, но соответствующий виджет в интерфейсе останется. Для полного отключения чата тех. поддержки обратитесь к вашему персональному менеджеру или в чат тех. поддержки ВКС. * allowusermedia - отдельно указанное разрешение, которое сообщает, что использование камеры и микрофона пользователем разрешено. Является устаревшим, однако может использоваться в старых версиях браузеров. * allow - список разрешений для комнаты, где через знак “;” перечисляются разрешенные для комнаты возможности. danger Внимание, ниже перечислен список всех необходимых разрешений для секции allow. В случае если вы не укажете какой-то из них, то часть функционала приложения может быть недоступна. Если для атрибута allow для какого-то разрешения добавить ключевое слово none, то это означает запрет на использование. Например, allow = camera `none` означает, что использование камеры будет запрещено. * camera - разрешено использование камеры; * microphone - разрешено использование микрофона; * fullscreen - разрешено отображение во весь экран; * accelerometer - разрешено использование акселерометра (на мобильных устройствах); * autoplay - разрешено автоматическое проигрывание; * clipboard-write - разрешено копирование в буфер обмена; * encrypted-media - разрешено использование так называемого Encrypted Media Extension API, который позволяет управлять проигрыванием медиа контента на странице; * display-capture - разрешено использовать захват содержимого экрана; * gyroscope - разрешено использование гироскопа; * picture-in-picture - разрешено использование режима “картинка-в-картинке”; * screen-wake-lock - заблокировано отключение экрана со временем, если открыта страница с включенным элементом iFrame; * compute-pressure - разрешено использование API для получения данных о нагрузке на устройство, таких как температура, использование процессора и батареи. ### Content Security Policy (CSP)[​](#content-security-policy-csp "Direct link to Content Security Policy (CSP)") warning Внимание, если на вашем сайте вы устанавливаете CSP-заголовки, то убедитесь, что вы указали все необходимые разрешения для домена `*.livedigital.space` Подробнее о директивах CSP можно [прочитать в спецификации](https://developer.mozilla.org/ru/docs/Web/HTTP/CSP). info Если вы, к примеру, разрешили на уровне CSP обращения только к https\://edu.livedigital.space, но запретили все остальные саб-домены, То можно столкнуться с проблемами невозможности отправки аналитики, открытия чата, использования функционала модератора и т.д. Проверить, какие именно заголовки установлены на вашем сайт, можно, используя инструмент [CSP-Evaluator](https://csp-evaluator.withgoogle.com/) ### Дополнительные query параметры ссылки в iframe[​](#дополнительные-query-параметры-ссылки-в-iframe "Direct link to Дополнительные query параметры ссылки в iframe") * `theme` - light/dark, задаёт светлую/тёмную цветовую схему. Без указания будет использована системная. * `hideLeaveButton` - скрыть кнопку выхода из комнаты. ### Отправка сообщения о начале звонка в iFrame[​](#отправка-сообщения-о-начале-звонка-в-iframe "Direct link to Отправка сообщения о начале звонка в iFrame") После старта сущности звонка (участник встречи подключился к комнате) ВКС отправит в родительское окно iFrame сообщение: ``` window.parent.postMessage('ld_start_call'); ``` Чтобы подписаться на это событие, необходимо добавить слушателя message к объекту window, как указано в [спецификации](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage): ``` function receiveMessage(event) { if (event.message === 'ld_start_call') { // do something } } window.addEventListener("message", receiveMessage, false); ``` ### Переадресация пользователя из iFrame по окончанию звонка[​](#переадресация-пользователя-из-iframe-по-окончанию-звонка "Direct link to Переадресация пользователя из iFrame по окончанию звонка") После завершения сущности звонка (все пользователи покинули комнату/администратор завершил звонок) ВКС отправит в родительское окно iFrame сообщение: ``` window.parent.postMessage('ld_finish_call'); ``` Чтобы подписаться на это событие, необходимо добавить слушателя message к объекту window, как указано в [спецификации](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage): ``` function receiveMessage(event) { if (event.message === 'ld_finish_call') { // do something } } window.addEventListener("message", receiveMessage, false); ``` tip Важно! Подписку на событие "message" необходимо выполнить только один раз, в противном случае функция "receiveMessage" будет выполняться столько раз, сколько вы её указали. warning Post-сообщения от iFrame к родителю также могут быть заблокированы политиками CSP вашего сайта. ### Управление устройствами[​](#управление-устройствами "Direct link to Управление устройствами") Для управления устройствами можно использовать следующие сообщения: * `ld_enable_camera` (включает камеру) * `ld_disable_camera` (выключает камеру) * `ld_enable_microphone` (включает микрофон) * `ld_disable_microphone` (выключает микрофон) * `ld_report_devices_state` (получить информацию о текущем состоянии устройств) После выполнения команды будет отправлено соответствующее событие в родительское окно о результате её выполнения: * `ld_camera_enabled` (камера включена) * `ld_camera_disabled` (камера выключена) * `ld_microphone_enabled` (микрофон включен) * `ld_microphone_disabled` (микрофон выключен) * `ld_devices_state` (текущее состояние устройств) * `ld_device_error/ld_room_error` (ошибка при выполнении команды) Формат сообщения о состоянии устройств: ``` { event: 'ld_devices_state', camera: 'enabled' | 'disabled' | 'error', microphone: 'enabled' | 'disabled' | 'error' } ``` Формат сообщения об ошибке: ``` { event: 'ld_device_error', device: 'camera' | 'microphone', action: 'enable' | 'disable' } { event: 'ld_room_error', code: 'not_in_call' } ``` Пример кода, включающего камеру: ``` window.addEventListener('message', receiveMessage, false); function receiveMessage(evt) { const { data } = evt; if (data.event === 'ld_camera_enabled') { console.log('Камера включена'); } else if (data.event === 'ld_device_error' && data.device === 'camera') { console.log('Ошибка при включении камеры'); } } const iframe = document.querySelector('iframe'); if (iframe) { console.log('Включаем камеру...'); iframe.contentWindow.postMessage({event: 'ld_enable_camera'}, '*'); } ``` ### Управление участниками[​](#управление-участниками "Direct link to Управление участниками") Для получения списка участников в звонке используется сообщение `ld_get_participants` После выполнения команды будет отправлено событие `ld_participants_list` или `ld_room_error` в родительское окно о результате её выполнения. Формат события со списком участников: ``` enum RoomRole { Owner = 'role_room_owner', Moderator = 'role_room_moderator', CoModerator = 'role_room_co-moderator', Speaker = 'role_room_speaker', User = 'role_room_user', Guest = 'role_room_guest', } { event: 'ld_get_participants', } { event: 'ld_participants_list', participants: { id: string, name: string, role: RoomRole }[], } ``` ### Отображение участника в фулскрин[​](#отображение-участника-в-фулскрин "Direct link to Отображение участника в фулскрин") Используйте событие `ld_participant_enter_fullscreen`, чтобы слот с участником занял всё место в сетке. Только один участник может отображаться в этом виде. В качестве параметра `participantId` нужно указать идентификатор участника, который можно получить с помощью события `ld_get_participants` Для возврата к обычному отображению используйте событие `ld_participant_leave_fullscreen`. В результате выполнения будет отправлено событие `ld_participant_fullscreen_changed` или `ld_room_error` в родительское окно. Форматы событий: ``` { event: 'ld_participant_enter_fullscreen', participantId: string, } { event: 'ld_participant_leave_fullscreen', } { event: 'ld_participant_fullscreen_changed', view: 'enter_fullscreen' | 'leave_fullscreen', participantId: string, } ``` Пример кода, включающего отображение участника в фулскрин: ``` window.addEventListener('message', receiveMessage, false); function receiveMessage(evt) { const { data } = evt; if (data.event === 'ld_participants_list') { console.log('Список участников', data.participants); const participant = data.participants[0]; if (participant) { console.log('Переключение первого участника в сетке в фулскрин'); iframe.contentWindow.postMessage({ event: 'ld_participant_enter_fullscreen', participantId: participant.id }, '*'); } } else if (data.event === 'ld_participant_fullscreen_changed') { console.log('Участник изменил вид', data); } else if (data.event === 'ld_room_error') { console.log('Ошибка', data) } } const iframe = document.querySelector('iframe'); if (iframe) { console.log('Получение списка участников'); iframe.contentWindow.postMessage({event: 'ld_get_participants'}, '*'); } ``` --- # Распространённые варианты интеграции ### Общая информация[​](#общая-информация "Direct link to Общая информация") info Перед прочтением этого блока необходимо ознакомиться с общими [этапами интеграции](/docs/tutorials/integration_plan.md), зарегистрировать аккаунт и получить [персональный токен доступа](/docs/tutorials/auth/create_personal_token.md). tip Комбинируя подходы указанные ниже вы сможете выполнить все базовые задачи интеграции с вашим сервисом. danger Помните, что сервис ВКС не имеет так называемой "корзины" для сущностей API - все ресурсы, которые вы удаляете, не могут быть восстановлены. Будьте аккуратны. ### Проведение вебинара (или конференции) с постоянным спикером и незарегистрированными гостями[​](#проведение-вебинара-или-конференции-с-постоянным-спикером-и-незарегистрированными-гостями "Direct link to Проведение вебинара (или конференции) с постоянным спикером и незарегистрированными гостями") Для этого вам потребуется: 1. [Создать спэйс](/docs/tutorials/spaces.md) 2. [Создать комнату](/docs/tutorials/rooms.md) 3. Настроить [параметры входа в комнату](/docs/tutorials/rooms.md#%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9-%D0%B2%D1%85%D0%BE%D0%B4%D0%B0) 4. [Сформировать ссылку](/docs/tutorials/user_access/access_link.md#%D1%84%D0%BE%D1%80%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8-%D0%B4%D0%BB%D1%8F-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D0%B5%D0%B9) для [назначения прав](/docs/tutorials/user_access/access_link.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0) модератора или спикера и отдать её ведущему 5. Отдать ссылку на мероприятие для гостей ### Проведение закрытого вебинара (или конференции)[​](#проведение-закрытого-вебинара-или-конференции "Direct link to Проведение закрытого вебинара (или конференции)") Для этого вам потребуется: 1. [Создать спэйс](/docs/tutorials/spaces.md) 2. [Создать комнату](/docs/tutorials/rooms.md) со свойством *isPublic:false* 3. Настроить [параметры входа в комнату](/docs/tutorials/rooms.md#%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9-%D0%B2%D1%85%D0%BE%D0%B4%D0%B0) 4. [Сформировать ссылку](/docs/tutorials/user_access/access_link.md#%D1%84%D0%BE%D1%80%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B8-%D0%B4%D0%BB%D1%8F-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D0%B5%D0%B9) для [назначения прав](/docs/tutorials/user_access/access_link.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0) модератора или спикера и отдать её ведущему 5. [Сгенерировать временных пользователей](/docs/tutorials/user_access/generate_access.md) для гостей и разослать им ссылки ### Регистрация модераторов и пользователей из вашего сервиса в ВКС[​](#регистрация-модераторов-и-пользователей-из-вашего-сервиса-в-вкс "Direct link to Регистрация модераторов и пользователей из вашего сервиса в ВКС") 1. [Создать Oauth2-клиента](/docs/tutorials/auth/create_oauth_client.md) 2. [Получить токен доступа клиента](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BA%D0%BB%D0%B8%D0%B5%D0%BD%D1%82%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0) 3. [Зарегистрировать пользователя](/docs/tutorials/users.md#%D1%80%D0%B5%D0%B3%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%86%D0%B8%D1%8F-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F) в ВКС, сохранить его id и учётные данные в своей базе данных 4. [Выдать этому пользователю необходимые права](/docs/tutorials/user_access/intro.md) в зависимости от тех механик, которые вам более близки архитектурно. Относительно пункта №4 есть два основных варианта: * Все пользователи действуют в рамках [спэйсов](/docs/tutorials/spaces.md), которые вы создали для разных департаментов, но имеют [разные права](/docs/tutorials/user_access/space_invite.md), в зависимости от должности. В таком случае они самостоятельно управляют комнатами и [приглашают в них других пользователей](/docs/tutorials/user_access/access_link.md) - такой функционал доступен из UI [livedigital](https://edu.livedigital.space). * Вы используете один [спэйс](/docs/tutorials/spaces.md), но под каждый новый звонок [создаёте комнату](/docs/tutorials/rooms.md) с необходимыми настройками и [генерируете временных пользователей](/docs/tutorials/user_access/generate_access.md) и встариваете ВКС через [iFrame](/docs/tutorials/iframe.md), реализуя бесшовный доступ к ВКС с возможностью использования WhiteLabel и собственных логотипов. Ну или же вы можете скомбинировать любые подходы из раздела документации ["Доступ пользователей"](/docs/tutorials/user_access/intro.md) и создать схему интеграции, походящую для вас больше всего. ### Получение артефактов звонка (облачная запись, аналитика и т.д.)[​](#получение-артефактов-звонка-облачная-запись-аналитика-и-тд "Direct link to Получение артефактов звонка (облачная запись, аналитика и т.д.)") Для того чтобы в автоматическом режиме получать оповещения о том что звонок был начат или закончен, а так же что статус облачной записи был изменён, вам необходимо настроить [вебхук](/docs/tutorials/webhooks.md) для каждого вашего спэйса. note Так как вам может понадобиться разное поведение для разных спэйсов, мы не стали выносить настройки вебхуков под общие настройки мастер-аккаунта. К примеру в спэйсе А вам необходимо получать аналитику и видеозаписи (к примеру там происходит обучение или важные корпоративные мероприятия), а спэйс Б используется как "переговорка" - в таком случае сохранять все артефакты может быть не нужно. После создания [вебхуков](/docs/tutorials/webhooks.md), вы можете настроить на свой стороне сценарии, при которых по окончании звонка вы можете получить [аналитические данные](/docs/tutorials/analytics.md) об этом звонке, по externalUserId соотнести пользователей ВКС с пользователями из вашей системы, сохранить активность и вовлечённость пользователя в звонок. Получить ссылку на [просмотр или скачивание видео](/docs/tutorials/cloud_record.md#%D0%B2%D1%8B%D0%B3%D1%80%D1%83%D0%B7%D0%BA%D0%B0-%D0%B2%D0%B8%D0%B4%D0%B5%D0%BE-%D1%81-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C%D1%8E-%D0%B2%D0%B5%D0%B1%D0%B8%D0%BD%D0%B0%D1%80%D0%B0-%D0%B8%D0%BB%D0%B8-%D0%BA%D0%BE%D0%BD%D1%84%D0%B5%D1%80%D0%B5%D0%BD%D1%86%D0%B8%D0%B8), его транскрипции или только аудио-файлов и т.д. Возможно вам понадобятся [данные для построения дашбордов](/docs/tutorials/addtitional/dashboars.md) в вашем сервисе, для этого вы можете обогатить информацию о пользователях из ВКС идентификаторами из вашего сервиса. ### Перенаправление пользователя на другую страницу после завершения звонка[​](#перенаправление-пользователя-на-другую-страницу-после-завершения-звонка "Direct link to Перенаправление пользователя на другую страницу после завершения звонка") В зависимости от выбранного вами пути интеграции, вы можете использовать три варианта * Если используете [iFrame](/docs/tutorials/iframe.md), то можно использовать механизм post-сообщений. Подробнее [здесь](/docs/tutorials/iframe.md#%D0%BF%D0%B5%D1%80%D0%B5%D0%B0%D0%B4%D1%80%D0%B5%D1%81%D0%B0%D1%86%D0%B8%D1%8F-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8F-%D0%B8%D0%B7-iframe-%D0%BF%D0%BE-%D0%BE%D0%BA%D0%BE%D0%BD%D1%87%D0%B0%D0%BD%D0%B8%D1%8E-%D0%B7%D0%B2%D0%BE%D0%BD%D0%BA%D0%B0). * Переадресацию можно настроить для каждой комнаты в отдельности, см. пункт ["Описание параметров комнаты"](/docs/tutorials/rooms.md#%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%BE%D0%B2-%D0%BA%D0%BE%D0%BC%D0%BD%D0%B0%D1%82%D1%8B) и настройку *redirectUrl* * В случае если вы хотите перенаправлять участников со всех комнат вашего мастер-аккаунта, то можно обратиться в службу технической поддержки и попросить установить вам кастомный адрес перенаправления для всех звонков ### Сбор дополнительной информации о пользователях[​](#сбор-дополнительной-информации-о-пользователях "Direct link to Сбор дополнительной информации о пользователях") Зачастую в дополнение к переадресации пользователя после звонка, так же используется [настройка анкеты](/docs/tutorials/rooms.md#%D0%BD%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9-%D0%B2%D1%85%D0%BE%D0%B4%D0%B0) при входе в комнату. Она доступна как в вебинаре, так и в конференции и позволяет собрать любые данные, которые вы хотите позже увидеть в вашем [аналитическом отчёте](/docs/tutorials/analytics.md). ### Формирование ссылки для входа в комнату[​](#формирование-ссылки-для-входа-в-комнату "Direct link to Формирование ссылки для входа в комнату") Кроме встроенных механизмов формирования ссылок, к примеру [generate\_access](/docs/tutorials/user_access/generate_access.md) или [access\_link](/docs/tutorials/user_access/access_link.md) вы можете формировать ссылки самостоятельно. Формула для составления ссылки: `https://edu.livedigital.spaceroom/:room-alias?participantName=IvanIvanod` Где room-alias это alias вашей комнаты, подробнее [см. здесь](/docs/moodhood-api/v1/get-room.md). info Все query-параметры в url комнаты являются опциональными Список доступных параметров * **participantName** - имя участника * **email** - email участника * **phone** - телефон участника * **theme** - тема (dark или light) * **accessToken** - [токен доступа](/docs/tutorials/auth/get_tokens.md) пользователя * **refreshToken** - токен обмена доступа пользователя (если вы хотите сделать ссылку одноразовой) * **fhd** - повышенное качество камеры и демонстрации экрана (экспериментальный функционал) * **noheader** - отключение хэдера в комнате (экспериментальный функционал) * **utm\_source** - информация utm * **utm\_medium** - информация utm * **utm\_id** - информация utm * **utm\_content** - информация utm --- # Этапы интеграции Для начала работы необходимо зарегистрировать аккаунт на платформе ВКС и пройти в личный кабинет, затем в личном кабинете [создать и сохранить персональный токен](/docs/tutorials/auth/create_personal_token.md) - Personal Token. С помощью этого токена происходит авторизация при выполнении запросов к API. Очень важно использовать только тот персональный токен (personal token), который был сгенерирован владельцем аккаунта! Токены, которые были созданы с помощью других учетных записей не будут работать. Этап 1: Регистрация аккаунта: перейдите на сайт [livedigital](https://edu.livedigital.space) и [зарегистрируйте аккаунт](https://edu.livedigital.space/auth/signup?to=/), который будет использоваться для выполнения запросов к API. Этап 2: Получение персонального токена: [создайте токен](/docs/tutorials/auth/create_personal_token.md) и сохраните его в безопасном месте. Этот токен будет использоваться для авторизации при выполнении запросов к API. Этап 3: [Создание группы:](/docs/tutorials/spaces.md) используя полученный токен, выполните API-запрос для создания группы. Убедитесь, что передаете все необходимые параметры (например, название группы, описание и т.д.). Этап 4: [Создание комнаты](/docs/tutorials/rooms.md) для мероприятия: выполните API-запрос для создания комнаты, указывая идентификатор ранее созданной группы. Убедитесь, что указаны все необходимые параметры (например, название комнаты и т.д.). Этап 5: Ознакомьтесь с [распространёнными вариантами интеграции](/docs/tutorials/integration_cases.md) Этап 5: Ознакомьтесь [часто задаваемыми вопросами](/docs/tutorials/faq.md) Этап 6: Авторизация участников: выполните API-запросы для [добавления участников в комнату](/docs/tutorials/user_access/intro.md), используя их учетные данные и роли (например, модерато, ведущий и т.д.). Этап 7: Проведение мероприятия: мероприятие начнётся после того, как первый участник или администратор подключится к комнате. Этап 8: Передача аналитики и видеозаписи: после [завершения мероприятия](/docs/tutorials/webhooks.md) выполните запрос для [получения аналитики](/docs/tutorials/analytics.md) и [видеозаписи](/docs/tutorials/cloud_record.md). Сохраните полученные данные в нужном формате (например, JSON/XLSX для аналитики, MP4 для видеозаписи). Этап 9: Ознакомьтесь с [дополнительной информацией](/docs/tutorials/addtitional/dashboars.md) --- # Выбор способа интеграции Прежде чем начать, важно выбрать подходящий способ встраивания видеозвонков — от этого зависят объём работ и архитектура решения. Livedigital предоставляет несколько независимых путей интеграции. *** ## iFrame — быстрый старт[​](#iframe--быстрый-старт "Direct link to iFrame — быстрый старт") Самый простой способ добавить видеозвонки на сайт или в веб-приложение. Готовый интерфейс видеокомнаты встраивается одной строкой HTML — вы задаёте параметры через URL, всё остальное берёт на себя платформа. Помимо iFrame, для управления комнатами, участниками и токенами потребуется бэкенд, который будет обращаться к Moodhood REST API — например, создавать комнату перед звонком или выпускать ссылку доступа для пользователя. **Что вы получаете:** * Готовый видеоинтерфейс без разработки UI * Быстрый запуск — наши клиенты завершают iFrame + API интеграцию в среднем за **3 дня** (включая авторизацию, управление комнатами, вебхуки) * Минимальный фронтенд **Ограничения:** * Внешний вид ограничен настройками платформы * Кастомизация — только через параметры URL и настройки комнаты [Посмотреть руководство по iFrame →](/docs/tutorials/iframe.md) [Изучить REST API →](/docs/tutorials/intro.md) *** ## Web SDK — полный контроль[​](#web-sdk--полный-контроль "Direct link to Web SDK — полный контроль") `@livedigital/client` — это npm-пакет для TypeScript/JavaScript, который управляет WebRTC-соединением, медиапотоками, сигналингом и сетевой устойчивостью. SDK намеренно **не содержит UI-компонентов** — ни кнопок, ни сетки участников, ни панели управления. Это транспортный уровень, а не UI-библиотека. **Что придётся сделать самостоятельно:** * Спроектировать и сверстать интерфейс с нуля: галерея участников, кнопки управления камерой и микрофоном, экран ожидания, индикаторы качества соединения * Принять UX-решения: как пользователи входят в комнату, что происходит при потере соединения, как выйти, как поднять руку * Обеспечить адаптивность, доступность и поддержку мобильных браузеров **В обмен — полная свобода.** Вы реализуете любой функционал в точности так, как нужно вашему продукту: кастомные реакции, собственная система ролей, уникальные UI-сценарии, глубокая интеграция в дизайн-систему. **Что берём на себя мы:** вся инфраструктурная сложность остаётся на нашей стороне — масштабирование под пиковые нагрузки, географически распределённые медиасерверы, отказоустойчивость, мониторинг качества соединения и своевременные обновления WebRTC-стека. Вы пишете продукт — мы обеспечиваем транспорт. **Оценка времени:** * \~1 неделя — базовая интеграция: подключение к комнате, публикация треков, отображение участников * 2–4 недели — полноценный UI со всеми сценариями [Начать с Web SDK →](/docs/web-sdk/quickstart.md) *** ## Mobile SDK — нативные приложения[​](#mobile-sdk--нативные-приложения "Direct link to Mobile SDK — нативные приложения") Для мобильных приложений предусмотрены нативные SDK. Как и Web SDK, они предоставляют транспортный уровень без готового UI — видеоинтерфейс разрабатывается на стороне приложения. * **iOS / iPadOS** — XCFramework для Swift * **Android** — для Kotlin/Java [iOS SDK →](/docs/ios-sdk/intro.md) · [Android SDK →](/docs/android-sdk/intro.md) *** ## Только REST API[​](#только-rest-api "Direct link to Только REST API") Если задача не требует клиентской части — например, автоматическое управление комнатами, серверная запись, аналитика или интеграция с внешними системами — достаточно работать напрямую с Moodhood REST API без SDK. [Перейти к REST API →](/docs/tutorials/intro.md) *** ## Сравнение подходов[​](#сравнение-подходов "Direct link to Сравнение подходов") | | iFrame | Web SDK | Mobile SDK | | -------------- | --------------------- | ----------------------- | ----------------- | | Готовый UI | ✅ | ❌ пишется с нуля | ❌ пишется с нуля | | Стек | HTML + бэкенд для API | TypeScript / JavaScript | Swift / Kotlin | | Кастомизация | Параметры URL | Полная | Полная | | Оценка времени | \~3 дня | \~1 нед. (базис) | \~1 нед. (базис) | --- # Введение Документ описывает порядок действий для добавления на сайт ВКС с помощью предоставляемого API. Методы API позволяют создать пространство (спэйс, группа) и комнаты для видеоконференций, настроить параметры их работы, добавить пользователей и управлять правами доступа для них к созданным комнатам. ### Работа с API[​](#работа-с-api "Direct link to Работа с API") API предоставляет собой набор методов из HTTP-запросов. Выполняя запрос с определенными параметрами, можно создать [новую группу](/docs/tutorials/spaces.md) или [комнату](/docs/tutorials/rooms.md), сформировать [токен пользователя](/docs/tutorials/auth/get_tokens.md) для доступа в группу или комнату при проведении [трансляции](/docs/tutorials/cloud_record.md#%D1%81%D1%82%D1%80%D0%B8%D0%BC%D0%B8%D0%BD%D0%B3-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8-%D0%BD%D0%B0-%D0%B4%D1%80%D1%83%D0%B3%D0%B8%D0%B5-%D1%81%D0%B5%D1%80%D0%B2%D0%B8%D1%81%D1%8B-%D0%BF%D0%BE%D1%81%D1%80%D0%B5%D0%B4%D1%81%D1%82%D0%B2%D0%BE%D0%BC-rtmp) и т.д. Поддерживается выполнение только авторизованных запросов. Для этого в заголовке запроса передается специальный токен доступа - [персональный токен](/docs/tutorials/auth/create_personal_token.md) (personal-token). Такой токен является уникальным для каждого [клиента](/docs/tutorials/auth/create_oauth_client.md) или [пользователя](/docs/tutorials/users.md). При этом для выполнения одних запросов нужно [авторизоваться как клиент](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BA%D0%BB%D0%B8%D0%B5%D0%BD%D1%82%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0), а для выполнения других [как пользователь](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0). ### Авторизация[​](#авторизация "Direct link to Авторизация") Для работы с API ВКС необходима авторизация. Авторизация осуществляется с помощью [персонального токена](/docs/tutorials/auth/create_personal_token.md) (personal-token) доступа. Мы рекомендуем использовать один аккаунт для создания персонального токена, и только этот аккаунт будет использоваться в настройке интеграции. Администраторам/организаторам встреч не требуется создание личного аккаунта. Подробнее об авторизации можно прочитать [здесь](/docs/tutorials/auth/auth_intro.md) ### Глоссарий и компоненты API[​](#глоссарий-и-компоненты-api "Direct link to Глоссарий и компоненты API") * [Группа (спэйс)](/docs/tutorials/spaces.md) - место, где группируются ваши [комнаты](/docs/tutorials/rooms.md); там же можно настроить [вебхуки](/docs/tutorials/webhooks.md) для отслеживания активности. * [Комната](/docs/tutorials/rooms.md) - место проведения вебинаров или конференций. * [Oauth2-клиент](/docs/tutorials/auth/create_oauth_client.md) - учётная запись для вашей интеграции, которая позволяет регистрировать новых пользователей. * [Токен доступа](/docs/tutorials/auth/get_tokens.md) - [JWT-токен](https://oauth.net/2/jwt/) используемый для авторизации пользователя или клиента в API. * [Пользователь](/docs/tutorials/users.md) - учётная запись участника, может быть постоянной или [временной](/docs/tutorials/user_access/generate_access.md). * [Вебхук](/docs/tutorials/webhooks.md) - обратный вызов от нашей API посредством выполнения POST-запроса по указанному адресу с целью оповещения о состоянии звонка или [облачной записи](/docs/tutorials/cloud_record.md) * [Чат](/docs/tutorials/chat.md) - компонент ВКС, отвечающий за переписку пользователей и [проведение опросов](/docs/tutorials/polls.md) * [Инвайты](/docs/tutorials/user_access/intro.md) - средство приглашения в [спэйс](/docs/tutorials/spaces.md) или [комнату](/docs/tutorials/rooms.md) * [Облачная запись](/docs/tutorials/cloud_record.md) - средство записи происходящего в комнате, или стриминг на внешние сервисы посредством RTMP * [iFrame](/docs/tutorials/iframe.md) - HTML-директива для размещения приложения ВКС на вашем сайте. * [Аналитика](/docs/tutorials/analytics.md) - аналитический отчёт, формируемый по окончании звонка. --- # Опросы Зачастую бывает полезно перед стартом звонка создать необходимые опросы в комнате, а [после его завершения](/docs/tutorials/webhooks.md) автоматически [получить аналитику](/docs/tutorials/analytics.md) и результаты. Всего на данный момент предусмотрено два метода работы с опросами: * [Создать новый опрос](/docs/moodhood-api/v1/create-a-new-poll.md) * [Получить все опросы в комнате](/docs/moodhood-api/v1/get-a-room-polls.md) tip Для всех действий с опросами вам понадобится постоянный **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен доступа или временный токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. Поскольку опросы относятся к административным ресурсам комнаты, пользователь, от имени которого выполняется запрос к API, должен обладать правами администратора или владельца этой комнаты или спэйса (если комната публичная). Если комната приватная, этот функционал доступен только пользователю с правами доступа к данной конкретной комнате. #### Описание параметров опросов:[​](#описание-параметров-опросов "Direct link to Описание параметров опросов:") * **question** - текст, отображаемый в опросе (вопрос для голосования); * **withFreeAnswer** - позволяет пользователям вводить свой вариант ответа; * **withCorrectAnswer** - указывает, есть ли в опросе правильные ответы; * **options** - массив вариантов ответов; * **value** - текст варианта ответа; * **isCorrect** - является ли вариант правильным ответом. #### Структура вариантов ответа (options)[​](#структура-вариантов-ответа-options "Direct link to Структура вариантов ответа (options)") ``` { "value": "Текст варианта ответа", "isCorrect": false } ``` ### Особенности параметра `withFreeAnswer`[​](#особенности-параметра-withfreeanswer "Direct link to особенности-параметра-withfreeanswer") Параметр `withFreeAnswer` позволяет создать опрос, в котором пользователи могут вводить свои собственные варианты ответов, не ограничиваясь предложенными вариантами. Будет работать только в том случае, если отсутствуют другие параметры. warning Удаления опросов или их запуска на данный момент не реализовано. #### Примеры использования:[​](#примеры-использования "Direct link to Примеры использования:") 1. Тест: ``` { "question": "Столица Франции?", "withFreeAnswer": false, "withCorrectAnswer": true, "options": [ {"value": "Париж", "isCorrect": true}, {"value": "Лондон", "isCorrect": false}, {"value": "Берлин", "isCorrect": false} ] } ``` 2. Cвободный опрос: ``` { "question": "Что вы думаете о новом продукте?", "withFreeAnswer": true, "options": [] } ``` 3. Опрос: ``` { "question": "Как дела?", "withFreeAnswer": false, "withCorrectAnswer": false, "options": [ { "value": "Хорошо" }, { "value": "Отлично" }, { "value": "Пока не родила" } ] } ``` #### Получение результатов опросов:[​](#получение-результатов-опросов "Direct link to Получение результатов опросов:") Для выгрузки аналитики по опросам в комнате используется метод [analyticsRoomPolls](/docs/moodhood-api/v1/analytics-room-polls.md), который возвращает данные аналитики только в формате **JSON**. В параметрах метода указывается идентификатор вызова callId, который можно получить с помощью метода [analyticsCalls](/docs/moodhood-api/v1/analytics-calls.md). Пример выполнения запроса [analyticsRoomPolls](/docs/moodhood-api/v1/analytics-room-polls.md): `GET /spaces/:spaceId/analytics/room-polls?roomId=:roomId&callId=:callId` В результате выполнения запроса возвращается аналитика по событию в формате **JSON**. Для получения callId нужно использовать метод [analyticsCalls](/docs/moodhood-api/v1/analytics-calls.md). В строке запроса в качестве части URL обязательно передается идентификатор группы spaceId, а также в качестве обязательных параметров запроса передается идентификатор комнаты и дата, за которую надо получить аналитику, - параметры roomId и date. Дополнительно можно указать, например, фильтр по минимальному количеству участников события. Пример **JSON** с данным о проведенном в звонке опросе: ``` { "records": [ { "clientUniqueId": "id", "userName": "(livedigital)", "participantId": "id", "externalUserId": "", "externalMeetingId": null, "email": "email", "ip": "id", "phone": "id", "deviceType": "desktop", "deviceModel": "unknown", "platform": "windows", "platformVersion": "10", "browser": "chrome", "browserVersion": "144.0.0.0", "referer": "https://edu.livedigital.space/space/id", "utmSource": null, "utmMedium": null, "utmCampaign": null, "utmContent": null, "utmTerm": null, "totalOnlineDuration": 820, "totalOnlineDurationWithActiveTab": 288, "totalActiveMicrophoneDuration": 0, "totalActiveCameraDuration": 0, "totalDemonstrationsDuration": 0, "reactionsCount": 0, "thumbUpCount": 0, "thumbDownCount": 0, "heartCount": 0, "fireCount": 0, "connectionDate": "2026-02-06 09:30:54", "lastOnlineDate": "2026-02-06 09:44:35", "chatMessages": [], "chatMessagesCount": 0, "surveyCount": 5, "surveyQuestion1": { "question": "абобаабобаабобаабоба", "answer": "2", "isCorrect": null }, "surveyQuestion2": { "question": "Столица Франции?", "answer": "Париж", "isCorrect": true }, "surveyQuestion3": { "question": "Столица Франции?", "answer": "Абуджа", "isCorrect": false }, "surveyQuestion4": { "question": "Как дела?", "answer": "Хорошо", "isCorrect": null }, "surveyQuestion5": { "question": "Как дела?", "answer": "Хорошо\nНормально", "isCorrect": null } } ] } ``` --- # Комнаты Представляют собой аналоги виртуальных переговорных. Комнаты бывают двух типов и отличаются целью использования и набором инструментов. * Конференция * Вебинар info Режим конференции, это стандартный режим звонка, общей численностью до 1 000 участников, где каждый может включить камеру, микрофон или иные виды демонстраций, если это [разрешено в настройках](/docs/tutorials/user_access/device_restrictions.md). Режим вебинара имеет возможность размещения до 10 000 участников, одновременно на экране находятся только три участника. Так же можно предоставлять другим пользователям возможность управлять вашей комнатой, [приглашать](/docs/tutorials/user_access/room_invite.md) туда новых пользователей, [выдавать им права](/docs/tutorials/user_access/access_link.md) на эту комнату, или [генерировать временных пользователей](/docs/tutorials/user_access/generate_access.md). Количество комнат которое может создать пользователь в спэйсе не ограничено, так же есть возможность [поиска комнаты по имени](/docs/moodhood-api/v1/get-rooms-list.md). ### Создание комнаты[​](#создание-комнаты "Direct link to Создание комнаты") Для создания необходимо выполнить запрос к эндпоинту [/spaces/:spaceId/rooms](/docs/moodhood-api/v1/create-room.md). Обратите внимание, что в запросе необходим идентификатор спэйса, в котором вы хотите создать комнату, т.е. API носит RESTful-характер, и для операций с вложенными объектами вам всегда нужен будет идентификатор его родителя. `POST /spaces/:spaceId/rooms` info Для всех действий с комнатой вам понадобится постоянный **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен доступа или временный токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. За исключением [запроса на чтение](/docs/moodhood-api/v1/get-room.md), если эта комната является публичной. Тело запроса: ``` { "name": "string", "templateId": "string", "isPublic": true, "isChatAllowed": true, "isRecordAllowed": true, "isAutoRecordingAllowed": false, "isMicrophonePublishingAllowed": true, "isScreenMediaPublishingAllowed": true, "isCustomMediaPublishingAllowed": true, "isCameraPublishingAllowed": true, "isAvatarsAllowed": true, "isSelfRenamingAllowed": true, "isHandRaisingAllowed": true, "isScreensharingAllowed": true, "isRemoteDrawingAllowed": true, "type": "lesson", "waitingRoomAudience": "nobody", "parentRoomId": "60d55c0eb9ef88ab17aabb12", "redirectUrl": "https://developer.mozilla.org/docs/Web/HTTP/Overview" } ``` info Обязательным параметром является только поле **name**, подробнее в [схеме](/docs/moodhood-api/v1/create-room.md) #### Описание параметров комнаты:[​](#описание-параметров-комнаты "Direct link to Описание параметров комнаты:") * **name** - Отображаемое имя пользователя * **isPublic** - Признак публичности комнаты. В случае isPublic :false доступ к комнате имеет создатель группы и те пользователи, которым был [предоставлен к ней доступ](/docs/tutorials/user_access/generate_access.md); * **isChatAllowed** - Включен/выключен чат у всех пользователей; * **isRecordAllowed** - Настройка видимости кнопки облачной записи в комнате; * **isAutoRecordingAllowed** - Настройка отвечающая за [автоматическую запись](/docs/tutorials/cloud_record.md#%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8) в комнате; * **isMicrophonePublishingAllowed** - Настройка отвечающая за возможность включать микрофон; * **isScreenMediaPublishingAllowed** - Настройка отвечающая за возможность включать демонстрацию экрана; * **isCustomMediaPublishingAllowed** - Настройка отвечающая за возможность включать демонстрацию пользовательских медиа, например PDF-файлов; * **isCameraPublishingAllowed** - Настройка отвечающая за возможность включать камеру; * **isAvatarsAllowed** - Настройка отвечающая за отображение аватаров пользователей в комнате (функционал на данный момент не доступен); * **isSelfRenamingAllowed** - Настройка отвечающая за возможность участникам переименовывать себя (не распространяется на модераторов); * **isHandRaisingAllowed** - Настройка отвечающая за возможность поднятия рук участниками (функционал на данный момент не доступен); * **isScreensharingAllowed** - Настройка отвечающая за доступность включения демонстрации (распространяется на все роли участников); * **isRemoteDrawingAllowed"** - Настройка отвечающая за доступность функционала удалённого управления (распространяется на все роли участников); * **type** - Тип комнаты: webinar (вебинар) или lesson (конференция). Подробнее [в схеме](/docs/moodhood-api/v1/create-room.md#request) * **waitingRoomAudience** - Включение зала ожидания в комнате. Подробнее [в схеме](/docs/moodhood-api/v1/create-room.md#request) * **redirectUrl** - URL по которому будут перенаправлены участники (не модераторы) после завершения звонка. ### Редактирование комнаты[​](#редактирование-комнаты "Direct link to Редактирование комнаты") Для редактирования необходимо выполнить запрос к эндпоинту [/spaces/:spaceId/rooms/:roomId](/docs/moodhood-api/v1/update-room.md). Обратите внимание, что в запросе необходим идентификатор как комнаты, так и спэйса, т.е. API носит RESTful-характер и для операций с вложенными объектами вам всегда нужен будет идентификатор его родителя. `PUT /spaces/:spaceId/rooms/:roomId` info Для всех действий с комнатой вам понадобится **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен или токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. За исключением [запроса на чтение](/docs/moodhood-api/v1/get-room.md), если эта комната является публичной. tip Все изменения вступают в силу немедленно и будут применены к текущему звонку комнаты незамедлительно. danger Изменить тип комнаты (поле **type**) невозможно. ### Зал ожидания[​](#зал-ожидания "Direct link to Зал ожидания") Чтобы включить функционал зала ожидания, необходимо применить настройку **waitingRoomAudience** с одним из трёх возможных вариантов состояния: * **nobody** - Зал ожидания выключен; * **all** - Зал ожидания включен для всех, кроме модераторов; * **guests** - Зал ожидания включен только для гостей. ### Настройка полей входа[​](#настройка-полей-входа "Direct link to Настройка полей входа") Так же можно отдельно настроить поля анкеты, которую пользователь увидит входя в комнату. Кроме email, имени и телефон, так же можно указать кастомные поля, которые будут отображены в [аналитическом отчёте](/docs/tutorials/analytics.md) ![Поля входа](/assets/images/join_settings_fields-532bfbdd5d010db3401c55b812c14e97.jpeg) Эндпоинт для изменения [параметров входа в комнату](/docs/moodhood-api/v1/update-join-settings-fields.md) `PUT /spaces/:spaceId/rooms/:roomId/join-settings/fields` Пример тела запроса: ``` { "fields": [ { "slug": "email", "type": "email", "title": "Email", "enabled": false, "required": false, "description": "Your email" } ], "customFields": [ { "id": "abcdefae-7dec-11d0-a765-00a0c91eabcd", "type": "string", "title": "Favorite Color", "enabled": false, "required": false, "description": "Input your favorite color, please", "order": 0 } ] } ``` В данном случае мы отключили показ поля **email**, передав *enabled:false* для *slug:email* в массиве **fields**, и создали новое поле, передав объект в массив **customFields**, c типом *string* и подписью "Favorite color", которое является не обязательным для заполнения. * **slug** - Уникальный идентификатор поля, которое заполняет участник при входе. Может быть *userName*, *email* или *phone*. Поле *userName* добавляется автоматически для всех типов комнат; * **type** - Тип поля, должен соответствовать признаку *slug*, т.е. если *slug* = *phone*, то *type* = *phone*. Для *slug* = *userName* используется *type* = *text*; * **title** - Название поля входа. Для *slug* = *userName* используется значение *admin.classrooms.userName*, для *slug* = *email* необходимо указать значение *admin.classrooms.email*, для *slug* = *phone* укажите *admin.classrooms.phone*; * **enable** - Признак, который активирует поля входа *userName/email/phone*. Может быть *true* или *false*. Поле *userName* всегда имеет *enabled* = *true*; * **required** - Делает поля обязательным для заполнения участником. Может быть *true* или *false*. Подробнее о возможных параметрах [см. в схеме](/docs/moodhood-api/v1/update-join-settings-fields.md) ### Другие операции с комнатами[​](#другие-операции-с-комнатами "Direct link to Другие операции с комнатами") * [Получение списка комнат](/docs/moodhood-api/v1/get-rooms-list.md) * [Удаление комнаты](/docs/moodhood-api/v1/delete-room.md) * [Приглашения в комнату](/docs/tutorials/user_access/room_invite.md) * [Получить информацию о комнате по её alias](/docs/moodhood-api/v1/get-room-by-alias.md) * [Количество комнат в спэйсе](/docs/moodhood-api/v1/count-available-rooms.md) --- # Спэйсы (группы) Группа, или же в терминологии API «спэйс» (space) - это иерархическая сущность, которая позволяет отделить ваши комнаты по определённому признаку, к примеру, выделить на каждую группу сотрудников по своему пространству, в рамках которого они смогут самостоятельно управлять комнатами и проводить вебинары или конференции. Также можно предоставлять другим пользователям возможность управлять вашим спэйсом, [приглашать](/docs/tutorials/user_access/space_invite.md) новых пользователей, создавать или удалять комнаты. Помимо иерархического смысла, спэйсы носят также функциональный характер. К примеру, [настроить вебхуки](/docs/tutorials/webhooks.md), при помощи которых можно отслеживать активность в комнатах. Количество спэйсов, которое может создать пользователь, не ограничено. Также есть возможность [поиска спэйса по имени](/docs/moodhood-api/v1/get-spaces-list.md). ### Создание спэйса[​](#создание-спэйса "Direct link to Создание спэйса") Для создания необходимо выполнить запрос к эндпоинту [/spaces](/docs/moodhood-api/v1/create-space.md). POST `https://moodhood-api.livedigital.space/v1/spaces` info Для всех действий со спэйсом вам понадобится токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. За исключением [запроса на чтение](/docs/moodhood-api/v1/get-space.md), если этот спэйс является публичным. С телом: ``` { "isPublic": string, "name": string, "description": string, "logo": string } ``` * isPublic - параметр отвечает за то, будут ли видны комнаты внутри спэйса всем пользователям или только тем, у кого есть на него права. * name - отображаемое имя * description - описание спэйса, выводимое в интерфейсе, может облегчить навигацию * logo - строка URL, ведущая к изображению с вашим логотипом Подробнее с параметрами можно ознакомиться в [схеме запроса](/docs/moodhood-api/v1/create-space.md) info Только поле **name** является обязательным Поле **logo** будет иметь эффект только на тарифах, которые имеют функцию WhiteLabel ### Доступ пользователей к спэйсу[​](#доступ-пользователей-к-спэйсу "Direct link to Доступ пользователей к спэйсу") Подробнее о том, как предоставить или забрать доступ пользователю к тому или иному спэйсу, можно прочитать в разделе «Доступ пользователей» ### Другие действия со спэйсом[​](#другие-действия-со-спэйсом "Direct link to Другие действия со спэйсом") Кроме создания также можно: * [Получить список спэйсов пользователя](/docs/moodhood-api/v1/get-spaces-list.md) (в ответе не только созданные пользователем спэйсы, но и те, в которые ему выдали доступ другие пользователи); * [Получить информацию о конкретном спэйсе](/docs/moodhood-api/v1/get-space.md) (может быть доступно в том числе и гостям); * [Изменить спэйс](/docs/moodhood-api/v1/update-space.md); * [Удалить спэйс](/docs/moodhood-api/v1/delete-space.md) (доступно только владельцу спэйса, т.е. тому, кто этот спэйс создал). info Для всех действий со спэйсом вам понадобится токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. За исключением [запроса на чтение](/docs/moodhood-api/v1/get-space.md), если этот спэйс является публичным. --- # Получение и выгрузка транскрибации и саммаризации видеозаписи Транскрибация представляет собой процесс преобразования видеозаписи вашей встречи в текстовый формат, что позволяет сохранить важные детали обсуждений, лекций или интервью. Саммаризация, в свою очередь, заключается в сокращении объемного текста до его основных идей и ключевых моментов. Это особенно актуально в условиях избытка информации, когда необходимо быстро извлечь суть без необходимости прочтения всего материала. Саммаризация помогает пользователям сосредоточиться на наиболее значимых аспектах текста, экономя время и усилия. warning Если функция транскрибации и саммаризации недоступна, то обратитесь в технческую поддержку ВКС или к персональному менеджеру. ## Создание транскрибации (расшифровки) и саммари[​](#создание-транскрибации-расшифровки-и-саммари "Direct link to Создание транскрибации (расшифровки) и саммари") По умолчанию функция транскрибации распространяется на все видеозаписи в аккаунте, но если необходимо создать транскрибацию и саммари для определенной видеозаписи, то вам поможет POST-метод [transcribeRecord](/docs/moodhood-api/v1/transcribe-record.md). Данный метод в течение некоторого времени подготавливает транскрибацию и, опционально, саммаризацию, которую в дальнейшем можно будет выгрузить. Для создания транскрибации transcribeRecord следующие параметры: spaceId, roomId и recordId. Для создания саммари добавьте `makeSummary=true`. Пример выполнения запроса transcribeRecord: `POST` `https://moodhood-api.livedigital.space/v1/spaces/60d55c0eb9ef88ab17aabb12/rooms/60d55c0eb9ef88ab17aabb12/records/60d55c0eb9ef88ab17aabb12/transcribe?makeSummary=true` В теле ответа будут переданы данные о видеозаписи, к которой применилось создание транскрибации: ``` { "id": "67fcb1325ab0b01e968d4d9f", "name": "string-2025", "fileSize": 747600, "roomId": "67fcb1275ab0b02dab8d4d56", "spaceId": "67fcb1065ab0b0157e8d4d25", "state": "finished", "callId": "67fcb12e2764630628259d7b", "playbackEventAlias": null, "publicationStatus": "unpublished", "finishedAt": "2025-04-14T06:56:03.401Z", "duration": 76792, "withAudioTracksArchive": true } ``` info Когда ваша транскрибация и саммари будет завершена и готова к выгрузке, платформа отдаст вебхук [transcribation\_finished](/docs/moodhood-api/v1/create-web-hook.md). warning Если при создании транскрибации не был указан параметр `makeSummary=true`, то получение саммари для этой записи будет возможно повторно только один раз! Второй (и последний) вызов саммаризации доступен в общем порядке, когда первая транскрибация успешно выполнилась. ## Получение и выгрузка[​](#получение-и-выгрузка "Direct link to Получение и выгрузка") Для получения транскрибации и саммаризации видеозаписи прошедшей встречи используется метод [GetRoomRecordTranscription](/docs/moodhood-api/v1/get-room-record-transcription.md), в котором требуется указать идентификатор группы (spaceId), идентификатор комнаты (roomId) и идентификатор записи, которую требуется транскрибировать (`recordId`). Полная спецификация метода приведена в [документации](/docs/moodhood-api/v1/get-room-record-transcription.md). По умолчанию в результате выполнения в ответе будет возвращена сама транскрибация (base64) встречи в виде сжатых данных gzip и саммари в текстовом формате. Чтобы получить полный текст транскрибации, выполните декодировку строки base64 и её декомпрессию или добавьте к GET-запросу опциональный query-параметр `decode=true` Пример выполнения запроса GetRoomRecordTranscription: `GET` `https://moodhood-api.livedigital.space/v1/spaces/60d55c0eb9ef88ab17aabb12/rooms/60d55c0eb9ef88ab17aabb12/records/60d55c0eb9ef88ab17aabb12/transcription?decode=true` Тело ответа: ``` { "transcription": { "segmentsBufferString": "H4sIAAAAAAAAA9XRQWpbMRAG2A/SQZBVzjCV7G6hEY", "segmentsCount": 5, "summary": "1. **Введение**: - Максим представил компанию, занимающуюся разработкой программного обеспечения с 2008 года." } } ``` Пример кода для cURL: ``` curl -X 'GET' \ 'https://moodhood-api.livedigital.space/v1/spaces/60d55c0eb9ef88ab17aabb12/rooms/60d55c0eb9ef88ab17aabb12/records/60d55c0eb9ef88ab17aabb12/transcription' \ -H 'accept: application/json' ``` --- # Ссылка временного доступа В случае если вам, в силу архитектуры вашего сервиса, не подходят сценарии с [приглашением пользователей в спэйс](/docs/tutorials/user_access/space_invite.md) или в [комнату](/docs/tutorials/user_access/room_invite.md), а так же [генерация временных пользователей](/docs/tutorials/user_access/generate_access.md), то можно воспользоваться функционалом создания ссылок временного доступа. Вы можете сгенерировать их в необходимом количестве для каждого вашего пользователя и управлять ими - к примеру деактивировать, если такая ссылка была скомпрометирована или этому пользователю более не нужно предоставлять доступ в эту комнату. ### Создание временного доступа[​](#создание-временного-доступа "Direct link to Создание временного доступа") Для создания доступа необходимо выполнить [запрос на генерацию](/docs/moodhood-api/v1/create-room-access-link.md) `POST /spaces/:spaceId/rooms/:roomId/access-link` info Для генерации временных временного доступа вам понадобится постоянный **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен доступа или временный токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. Генерировать доступ может только владелец этой комнаты или пользователь с правами администратора. Метод принимает в теле запроса три параметра * **name** - Имя пользователя * **role** - Роль, к примеру role\_room\_speaker или role\_room\_co-moderator * **type** - Тип доступа: call, one-time, permanent ### Назначения временных ролей[​](#назначения-временных-ролей "Direct link to Назначения временных ролей") * **role\_room\_speaker** - не обладает функционалом модератора, однако имеет неоспоримое право на [включение своих устройств вещания](/docs/tutorials/user_access/device_restrictions.md), а так же может зайти в комнату минуя [зал ожидания](/docs/tutorials/rooms.md#%D0%B7%D0%B0%D0%BB-%D0%BE%D0%B6%D0%B8%D0%B4%D0%B0%D0%BD%D0%B8%D1%8F), если такой включен * **role\_room\_co-moderator** - обладает всеми возможностями, что и обычный модератор, но не может выдавать временные права и не может запускать [автоматическую облачную запись](/docs/tutorials/cloud_record.md#%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). ### Разница типов временного доступа[​](#разница-типов-временного-доступа "Direct link to Разница типов временного доступа") * **call** - Действует только во время звонка, в который он был активирован. К примеру если звонок был завершён (все вышли из комнаты), то доступ аннулируется. * **one-time** - Одноразовый, по такой ссылке можно перейти только один раз. Если воспользоваться ей и переслать другому участнику, то второй раз ей воспользоваться не удастся. * **permanent** - Постоянный доступ, перейдя по такой ссылке можно получать временные права, пока доступ не будет [аннулирован](/docs/moodhood-api/v1/delete-room-access-link.md). ### Формирование ссылки для пользователей[​](#формирование-ссылки-для-пользователей "Direct link to Формирование ссылки для пользователей") Выполнив запрос на создание доступа вы не получите полноценную ссылку, которую можно отдать пользователя, вы получите только id созданного доступа. К примеру: ``` { "id": "60d55c0eb9ef88ab17aabb12", "name": "string", "roomId": "60d55c0eb9ef88ab17aabb12", "spaceId": "60d55c0eb9ef88ab17aabb12", "role": "role_room_speaker", "type": "one-time" } ``` Подробнее [см. в схеме](/docs/moodhood-api/v1/create-room-access-link.md) Здесь мы видим всю необходимую информацию о созданном доступе, однако теперь необходимо самостоятельно сгенерировать ссылку для входа участника. Она имеет следующий вид `https://edu.livdigital.space/room/:room-alias?accessLink=:accessLinkId`, где accessLinkId мы получили в ответе на запрос о создании доступа. Так же можно выполнить [запрос на получение всех сгенерированных доступов](/docs/moodhood-api/v1/get-room-access-link.md) в указанной комнате. Единственным недостающим компонентом является alias комнаты, его можно узнать выполнив запрос на [чтение комнаты](/docs/moodhood-api/v1/get-room.md) по её id. `GET /spaces/:spaceId/rooms/:roomId` info Для всех действий с комнатой вам понадобится токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. За исключением [запроса на чтение](/docs/moodhood-api/v1/get-room.md), если эта комната является публичной. ### Управление доступом[​](#управление-доступом "Direct link to Управление доступом") Так же по мере необходимости вы можете: * [Изменить доступ](/docs/moodhood-api/v1/update-room-access-link.md) по его id. К примеру изменить имя или роль ([тип доступа](/docs/tutorials/user_access/access_link.md#%D1%80%D0%B0%D0%B7%D0%BD%D0%B8%D1%86%D0%B0-%D1%82%D0%B8%D0%BF%D0%BE%D0%B2-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0) изменить нельзя) * [Удалить созданный ранее доступ](/docs/moodhood-api/v1/delete-room-access-link.md) info Для управления временным доступом вам понадобится токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. --- # Управление устройствами пользователя в комнате Для управления устройствами пользователей вы можете воспользоваться функционалом * Запрета на включение микрофона * Запрета на включение камеры * Запрета на включение демонстрации экрана * Запрета на включение демонстрации пользовательских медиа Функция полезна, когда вы проводите массовые мероприятия и необходимо обеспечить максимальную концентрацию участников. Например, чтобы они не могли несанкционированно включать микрофон или демонстрировать небезопасный контент. Также на данный момент в приложении есть техническая рекомендация не превышать количество одновременно включенных камер более чем 200 — в противном случае это может вызвать постепенное снижение качества изображения и звука. Таким образом, можно заранее настроить права доступа к устройствам для всех участников, не обладающих функционалом модератора. Эти параметры хранятся в [настройках комнаты](/docs/tutorials/rooms.md#%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%BE%D0%B2-%D0%BA%D0%BE%D0%BC%D0%BD%D0%B0%D1%82%D1%8B) * **isMicrophonePublishingAllowed** - Настройка, отвечающая за возможность включать микрофон * **isScreenMediaPublishingAllowed** - Настройка, отвечающая за возможность включать демонстрацию экрана * **isCustomMediaPublishingAllowed** - Настройка, отвечающая за возможность включать демонстрацию пользовательских медиа, например PDF-файлов * **isCameraPublishingAllowed** - Настройка, отвечающая за возможность включать камеру Для их применения необходимо выполнить запрос на [редактирование комнаты](/docs/tutorials/rooms.md#%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BA%D0%BE%D0%BC%D0%BD%D0%B0%D1%82%D1%8B), указав желаемые настройки в теле запроса. `PUT /spaces/:spaceId/rooms/:roomId` info Для всех действий с комнатой вам понадобится постоянный **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен доступа или временный токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. tip Все изменения вступают в силу немедленно и будут применены к текущему звонку комнаты незамедлительно. --- # Генерация временного пользователя При помощи метода [roomGenerateAccess](/docs/moodhood-api/v1/room-generate-access.md), который позволяет создать временного пользователя без необходимости регистрации на платформе ВКС, вы можете пригласить на встречу участника, наделенного правами администратора или не имеющего таковых. В строке запроса передается идентификатор группы и комнаты в ней, а в теле запроса имя пользователя и его роль: * **user** - гость. * **moderator** - администратор встречи. Дополнительно в теле запроса можно передать следующие параметры: * **externalUserId** - любой буквенно-циферный идентификатор пользователя, по которому интегратор может сопоставить пользователя системы с пользователем своего сервиса, например, в [аналитическом отчёте](/docs/tutorials/analytics.md). Рекомендуем создавать его уникальным для каждого пользователя, чтобы облегчить процесс идентификации в аналитическом отчёте; * **externalMeetingId** - любой буквенно-циферный идентификатор для конкретной встречи. Необязательный параметр, который позволяет LMS системе передавать уникальный для неё идентификатор каждой конкретной встречи, на которую приходит участник. Рекомендуем создавать его уникальным для каждого пользователя, чтобы облегчить процесс идентификации в [аналитическом отчёте](/docs/tutorials/analytics.md); * **ttl** - время жизни ссылки в секундах (по умолчанию 86400 секунд). Максимальное значение - 432000 секунд. Рекомендуем передавать длительность на 2 часа больше, чем ожидаемая длительность мероприятия; * **isPermanent** - позволяет перейти по пригласительной ссылке несколько раз, т.е. ссылка не будет аннулирована при повторном переходе. Если этот параметр отсутствует, то ссылка будет являться одноразовой; * **email** - эл. почта пользователя; * **phone** - телефон пользователя. tip При переходе в комнату по ссылке, которая будет сгенерирована в результате выполнения метода, у пользователя будут запрошены персональные данные, такие как его эл.почта, телефон и т.д. Эти данные потом используются в аналитических отчетах. Если какие-то данные известны заранее, то можно передать их в запросе, указав в теле запроса значения email, phone и другие поля, которые могут быть запрошены у пользователя на странице входа. Если эти данные были переданы в теле запроса, то они уже не запрашиваются у пользователя повторно на форме входа в вебинар. info По истечении указанного TTL временный пользователь будет безвозвратно удалён. Если перейти по ссылке с истёкшим TTL, то пользователь попадёт в комнату обладая правами гостя. Таким образом, если комната является приватной См. [настройки комнаты](/docs/tutorials/rooms.md#%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%B0%D1%80%D0%B0%D0%BC%D0%B5%D1%82%D1%80%D0%BE%D0%B2-%D0%BA%D0%BE%D0%BC%D0%BD%D0%B0%D1%82%D1%8B), то пользователь не сможет подключиться. Пример выполнения запроса [roomGenerateAccess](/docs/moodhood-api/v1/room-generate-access.md) `POST /spaces/:spaceId/rooms/:roomId/generate-access` info Для генерации временных пользователей и ссылок вам понадобится токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. Генерировать ссылку может только владелец этой комнаты. ``` { "username": "JohnDoe", "role": "moderator", "externalUserId": "string", "externalMeetingId": "string", "ttl": 86400, "isPermanent": true, "email": "john.doe@mail.server", "phone": "+9606646464" } ``` Пример кода cURL: ``` curl --location --request POST \ 'https://moodhood-api.livedigital.space/v1/spaces/62bcc725721aeb718445daf7/rooms/62bcc7341f211e444300da37/generate-access' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.*****.5vTOaqS77Yl-4cYT1WM5DdKbo8-IybvbxB2DX5kyFaTA' \ --header 'Content-Type: application/json' \ --data-raw ' { "username": "JohnDoe", "role": "user", "externalUserId": "string", "ttl": 86400, "isPermanent": true, "email": "john.doe@mail.server", "phone": "+9606646464" }' ``` В результате выполнения запроса пользователю задается роль для указанной комнаты и в теле ответа возвращается параметр url, содержащий ссылку для доступа вида: `https://edu.livedigital.spaceroom/hMInKY6FoM?participantName=JohnDoe&accessToken=someAccessToken` Ссылка содержит значения participantName, равное указанному в запросе параметру username, и уже сгенерированный токен пользователя accessToken, таким образом, нет необходимости отдельно его генерировать и подставлять в ссылку. --- # Самостоятельная генерация signaling-токена Signaling token — это JWT, который медиа-сервер livedigital использует для аутентификации участника при подключении к каналу. В токене зашита информация о том, в какой канал входит участник, какую роль он занимает и какие медиа-потоки имеет право публиковать. В стандартном сценарии интеграции токен выпускается через Moodhood API — методом [`createParticipantSignalingToken`](/docs/moodhood-api/v1/create-participant-signaling-token.md) после создания participant. Этот путь покрывает все облачные сценарии и не требует от интегратора знаний о внутренней структуре токена. Когда нужна самостоятельная генерация Этот раздел нужен только если вы используете **on-premise решение** и разворачиваете части стека livedigital у себя самостоятельно. В таком случае вы можете формировать signaling-токен своим бэкендом, минуя обращение к публичной Moodhood API — это сокращает цепочку зависимостей и позволяет тоньше управлять временем жизни и параметрами токена. Для облачной интеграции продолжайте использовать [Moodhood API](/docs/moodhood-api/v1/create-participant-signaling-token.md) — этот раздел можно пропустить. Секрет подписи Для самостоятельной генерации нужен симметричный секрет, синхронизированный с конфигурацией вашего медиа-сервера. Получите его у менеджера или администратора проекта и храните только на бэкенде — секрет даёт право выпускать токены от имени любого участника, в браузер или мобильное приложение его передавать нельзя. ## Формат токена[​](#формат-токена "Direct link to Формат токена") | Параметр | Значение | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Формат | JSON Web Token, Compact Serialization ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519), [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515)) | | Алгоритм подписи | `HS256` — HMAC SHA-256 ([RFC 7518 §3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-3.2)) | | Ключ | Симметричный, UTF-8 строка | Header токена всегда фиксированный: ``` { "alg": "HS256", "typ": "JWT" } ``` ## Структура payload[​](#структура-payload "Direct link to Структура payload") ### Стандартные claim-ы (Registered Claims)[​](#стандартные-claim-ы-registered-claims "Direct link to Стандартные claim-ы (Registered Claims)") Описаны в [RFC 7519 §4.1](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1). | Claim | Тип | Описание | | ----- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `iss` | string | Идентификатор тенанта, инициирующего вызов. Например, `"cft"` | | `sub` | string | Идентификатор того, кто звонит — клиент или колл-центр, по ситуации и направлению звонка | | `aud` | string | Роль участника. См. [§Роли участника](#%D1%80%D0%BE%D0%BB%D0%B8-%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0) | | `exp` | NumericDate (unix timestamp) | Время истечения. Рекомендуется выдавать короткоживущие токены | | `iat` | NumericDate (unix timestamp) | Время выпуска токена | ### Дополнительные claim-ы[​](#дополнительные-claim-ы "Direct link to Дополнительные claim-ы") Включаются непосредственно в тело payload: | Claim | Тип | Описание | | -------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `channelId` | `string` | Идентификатор канала. В сценарии звонка может совпадать с идентификатором абонента, которому нужно дозвониться | | `groups` | `string[]` | Группы прав участника. См. [§Группы прав](#%D0%B3%D1%80%D1%83%D0%BF%D0%BF%D1%8B-%D0%BF%D1%80%D0%B0%D0%B2) | | `producePermissions` | `string[]` | Разрешения на публикацию медиа-потоков. См. [§Разрешения на публикацию](#%D1%80%D0%B0%D0%B7%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BD%D0%B0-%D0%BF%D1%83%D0%B1%D0%BB%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8E) | | `externalCall` | `object` | Опционально. Помечает участника как сторону SIP-моста с внешней АТС (телефония). См. [§Звонок в АТС](#%D0%B7%D0%B2%D0%BE%D0%BD%D0%BE%D0%BA-%D0%B2-%D0%B0%D1%82%D1%81-externalcall) | ## Роли участника[​](#роли-участника "Direct link to Роли участника") Значение поля `aud` определяет, может ли участник публиковать медиа: | Значение | Описание | | ------------ | --------------------------------------------------------------------- | | `"host"` | Активный участник — может публиковать медиа-потоки и потреблять чужие | | `"audience"` | Слушатель — только потребляет медиа-потоки, публиковать не может | Эта роль маппится в SDK как `Role` — см. [руководство по ролям в Web SDK](/docs/web-sdk/webinar-mode.md#%D1%80%D0%BE%D0%BB%D0%B8-host-%D0%B8-audience). ## Группы прав[​](#группы-прав "Direct link to Группы прав") Значение поля `groups` определяет уровень прав участника внутри канала. Массив может содержать одно или несколько значений. | Значение | Описание | | ------------- | -------------------------------------- | | `"moderator"` | Привилегированный участник (модератор) | | `"user"` | Обычный участник | ## Разрешения на публикацию[​](#разрешения-на-публикацию "Direct link to Разрешения на публикацию") Значение `producePermissions` ограничивает, какие медиа-потоки участник имеет право публиковать. Массив может быть любым подмножеством перечисленных значений, включая пустой — в этом случае публикация полностью запрещена. | Значение | Описание | | ---------------- | ------------------------------------- | | `"camera"` | Видео с камеры | | `"microphone"` | Аудио с микрофона | | `"screen-video"` | Видео с экрана (демонстрация) | | `"screen-audio"` | Аудио экрана (демонстрация со звуком) | | `"custom-video"` | Кастомный видео-поток | | `"custom-audio"` | Кастомный аудио-поток | Минимум для голосового звонка Для голосового звонка достаточно роли `"host"` и разрешения `["microphone"]`. Для конференции с видео — `["camera", "microphone"]`. Для вебинара с демонстрацией экрана — `["camera", "microphone", "screen-video", "screen-audio"]`. ## Звонок в АТС (externalCall)[​](#звонок-в-атс-externalcall "Direct link to Звонок в АТС (externalCall)") Claim `externalCall` помечает участника как сторону **SIP-моста** между каналом и внешней АТС (телефония). По нему медиа-сервер понимает, что нужно навести мост и в каком направлении. Поле `direction` — явный дискриминатор, остальные поля зависят от направления: | Поле | Тип | Направление | Описание | | ----------- | ------------------------- | --------------- | -------------------------------------------------------------------- | | `direction` | `"inbound" \| "outbound"` | оба | Направление звонка относительно канала | | `sessionId` | `string` | inbound | Идентификатор сессии звонка — корреляция с уже идущим вызовом от АТС | | `callee` | `string` | outbound | Номер, на который медиа-сервер звонит через АТС | | `tenantId` | `string` | outbound | Тенант для выбора транка (→ заголовок `X-Tenant-Id`) | | `caller` | `string` | outbound (опц.) | Presented caller-id | ``` // входящий: внешняя АТС → участник канала "externalCall": { "direction": "inbound", "sessionId": "" } // исходящий: участник канала → внешний номер "externalCall": { "direction": "outbound", "callee": "+71234567890", "tenantId": "cft", "caller": "+70000000000" } ``` note Для **inbound** токен с `externalCall` минтит платформа (CRS) при дозвоне до абонента — связь со звонком идёт по `sessionId`. Для **outbound** значения `callee`/`tenantId` задаются на сервер-сайд (контроль права набора номера), не из клиента. ## Пример payload[​](#пример-payload "Direct link to Пример payload") ``` { "iss": "cft", "sub": "88003003000", "aud": "host", "iat": 1745395200, "exp": 1745481600, "channelId": "+79999999999", "groups": ["user"], "producePermissions": ["microphone"], "externalCall": { "direction": "inbound", "sessionId": "b3f1c2a4-..." } } ``` ## Процедура формирования токена[​](#процедура-формирования-токена "Direct link to Процедура формирования токена") В виде псевдокода: ``` secret = "<значение от менеджера>" // UTF-8 строка payload = { iss: tenantId, sub: caller, aud: role, // "host" | "audience" iat: now(), exp: now() + 86400, // +24 часа в секундах channelId: calleeId, groups: groups, // ["moderator"], ["user"] или их комбинация producePermissions: permissions // подмножество значений из раздела выше } token = JWT.sign(payload, secret, algorithm="HS256") ``` ### Пример на Node.js[​](#пример-на-nodejs "Direct link to Пример на Node.js") signaling-token.ts ``` import jwt from 'jsonwebtoken'; const SIGNALING_SECRET = process.env.LIVEDIGITAL_SIGNALING_SECRET!; export function issueSignalingToken(params: { tenantId: string; caller: string; channelId: string; role: 'host' | 'audience'; groups: Array<'moderator' | 'user'>; producePermissions: Array< | 'camera' | 'microphone' | 'screen-video' | 'screen-audio' | 'custom-video' | 'custom-audio' >; ttlSeconds?: number; }): string { const now = Math.floor(Date.now() / 1000); return jwt.sign( { iss: params.tenantId, sub: params.caller, aud: params.role, iat: now, exp: now + (params.ttlSeconds ?? 86400), channelId: params.channelId, groups: params.groups, producePermissions: params.producePermissions, }, SIGNALING_SECRET, { algorithm: 'HS256' }, ); } ``` ## Использование токена[​](#использование-токена "Direct link to Использование токена") Сформированный токен передаётся клиентскому приложению и используется при подключении к каналу: * **Web SDK** — параметр `token` метода [`client.join()`](/docs/web-sdk/connection.md#%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B8-%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F) * **iOS SDK** — параметр `signalingToken` метода [`connectToChannel`](/docs/ios-sdk/getting-started.md#%D1%81%D1%82%D0%B0%D1%80%D1%82-%D1%81%D0%B5%D1%81%D1%81%D0%B8%D0%B8) * **Android SDK** — параметр `signalingToken` в [`StockChannelSessionParams`](/docs/android-sdk/intro.md#connection) ## Библиотеки JWT для разных платформ[​](#библиотеки-jwt-для-разных-платформ "Direct link to Библиотеки JWT для разных платформ") | Язык | Библиотека | Ссылка | | ------- | --------------------------------- | -------------------------------------------------------------------------------------------------- | | Node.js | `jsonwebtoken` | [github.com/auth0/node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) | | Python | `PyJWT` | [pyjwt.readthedocs.io](https://pyjwt.readthedocs.io) | | Go | `golang-jwt/jwt` | [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) | | Java | `java-jwt` (Auth0) | [github.com/auth0/java-jwt](https://github.com/auth0/java-jwt) | | PHP | `firebase/php-jwt` | [github.com/firebase/php-jwt](https://github.com/firebase/php-jwt) | | Ruby | `ruby-jwt` | [github.com/jwt/ruby-jwt](https://github.com/jwt/ruby-jwt) | | C# | `System.IdentityModel.Tokens.Jwt` | [learn.microsoft.com](https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens) | Полный каталог JWT-библиотек — [jwt.io/libraries](https://jwt.io/libraries). --- # Введение Доступ к ресурсам API можно предоставлять несколькими способами. Например * [Приглашать пользователей в спэйс (группу)](/docs/tutorials/user_access/space_invite.md) * [Приглашать пользователей в комнату](/docs/tutorials/user_access/room_invite.md) * Предоставлять [временные права на комнату](/docs/tutorials/user_access/access_link.md) по сгенерированной ссылке для гостей и зарегистрированных участников. * [Генерировать временных пользователей](/docs/tutorials/user_access/generate_access.md) с различными правами в комнате Кроме того, можно регулировать доступ пользователей к управлению своими устройствами во время звонка. К примеру, предоставляя возможность выключить микрофон, камеру или демонстрацию экрана. Подробнее [здесь](/docs/tutorials/user_access/device_restrictions.md). Если вы используете **on-premise решение** и разворачиваете части livedigital у себя самостоятельно, то signaling-токен для подключения к медиа-каналу можно [генерировать своим бэкендом](/docs/tutorials/user_access/generate_signaling_token.md), минуя обращение к Moodhood API. --- # Приглашение в комнату Дать постоянный доступ зарегистрированному пользователю к комнате, можно двумя способами. 1. По аналогии с [приглашениями в спэйс](/docs/tutorials/user_access/space_invite.md), в таком случае это приглашение не является персональным и может быть активировано любым пользователем. 2. Назначить ему роль самостоятельно. В этом случае необходимо знать идентификатор пользователя, которому выдаёт доступ. ### Приглашение в комнату[​](#приглашение-в-комнату-1 "Direct link to Приглашение в комнату") Приглашение можно создать обратившись к эндпоинту [создания приглашений](/docs/moodhood-api/v1/create-room-invite.md) `POST /spaces/:spaceId/rooms/:roomId/invites` info Для всех действий с комнатой вам понадобится постоянный **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен доступа или временный токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. Генерировать приглашения может только владелец этой комнаты. В теле запроса необходимо указать роль пользователя ``` { "role": "role_room_moderator" } ``` В ответе будет получен id приглашения ``` { "id": "string" } ``` ### Роли пользователей[​](#роли-пользователей "Direct link to Роли пользователей") * **role\_room\_moderator** - Полноценный модератор комнаты, в отличие от [временного](/docs/tutorials/user_access/access_link.md#%D0%BD%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-%D1%80%D0%BE%D0%BB%D0%B5%D0%B9) обладает всем функционалом. * **role\_room\_user** - Обычный пользователь, может зайти в комнату, если она является приватной. Или может подключиться минуя [зал ожидания](/docs/tutorials/rooms.md#%D0%B7%D0%B0%D0%BB-%D0%BE%D0%B6%D0%B8%D0%B4%D0%B0%D0%BD%D0%B8%D1%8F), если тот включен. ### Активация приглашения[​](#активация-приглашения "Direct link to Активация приглашения") Активация можно произвести двумя способами: * Сгенерировать ссылку и отправить пользователю для его самостоятельной активации. Такая ссылка будет иметь вид `https://edu.livedigital.spaceinvite/:spaceId/:inviteId` * [Активировать этот инвайт](/docs/moodhood-api/v1/activate-invite.md) самостоятельно, используя **[токен доступа пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, которому необходимо выдать права к этой комнате. ### Удаление приглашения[​](#удаление-приглашения "Direct link to Удаление приглашения") Для удаления необходимо воспользоваться эндпоинтом `DELETE /spaces/:spaceId/invites/:inviteId` Подробнее [см. в схеме](/docs/moodhood-api/v1/delete-invite.md) info Обратите внимание, что методы которые могли бы показать вам список всех приглашений и их статусов отсутствуют. Т.е. если вы хотите отозвать какое-либо приглашение, то хранить его идентификатор необходимо на вашей стороне. ### Назначение роли[​](#назначение-роли "Direct link to Назначение роли") Для того чтобы назначить роль пользователю, необходимо знать его id. Если он уже имеет какую-то роль в указанном спэйсе, то можно [посмотреть список пользователей](/docs/moodhood-api/v1/get-space-users-list.md) в спэйсе и назначить ему необходимую роль, воспользовавшись [запросом](/docs/moodhood-api/v1/grant-role-to-user-at-room.md). `POST /spaces/:spaceId/rooms/:roomId/roles` В теле запроса необходимо указать [роль пользователя](/docs/tutorials/user_access/room_invite.md#%D1%80%D0%BE%D0%BB%D0%B8-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D0%B5%D0%B9) и его идентификатор. Например: ``` { "targetUserId": "60d55c0eb9ef88ab17aabb12", "role": "role_space_moderator" } ``` ### Удаление роли[​](#удаление-роли "Direct link to Удаление роли") Для того чтобы удалить роль пользователю, необходимо знать его id. Если он уже имеет какую-то роль в указанном спэйсе, то можно [посмотреть список пользователей](/docs/moodhood-api/v1/get-space-users-list.md) в спэйсе и удалить его роль, воспользовавшись [запросом](/docs/moodhood-api/v1/revoke-role-from-user-at-room.md). `DELETE /spaces/:spaceId/rooms/:roomId/roles` В теле запроса необходимо указать идентификатор пользователя: ``` { "targetUserId": "60d55c0eb9ef88ab17aabb12", } ``` --- # Приглашение в группу (спэйс) Приглашение можно создать обратившись к эндпоинту [создания приглашений](/docs/moodhood-api/v1/create-space-invite.md) `POST /spaces/:spaceId/invites` info Для генерации приглашений вам понадобится постоянный **[персональный](/docs/tutorials/auth/create_personal_token.md)** токен доступа или временный токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization. Генерировать приглашения может только владелец или модератор спэйса. В теле запроса необходимо указать роль пользователя ``` { "role": "role_space_moderator" } ``` В ответе будет получен id приглашения ``` { "id": "string" } ``` ### Роли пользователей[​](#роли-пользователей "Direct link to Роли пользователей") * **role\_space\_moderator** - Полноценный модератор комнаты, в отличие от [временного](/docs/tutorials/user_access/access_link.md#%D0%BD%D0%B0%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-%D1%80%D0%BE%D0%BB%D0%B5%D0%B9) обладает всем функционалом. * **role\_space\_user** - Обычный пользователь, может зайти в комнату, если она является приватной. Или может подключиться минуя [зал ожидания](/docs/tutorials/rooms.md#%D0%B7%D0%B0%D0%BB-%D0%BE%D0%B6%D0%B8%D0%B4%D0%B0%D0%BD%D0%B8%D1%8F), если тот включен. warning Важно! Если пользователь имеет назначенную роль в спэйсе, но пытается зайти в приватную комнату этого спэйса, то для этого ему должны быть [выданы права на эту комнату](/docs/tutorials/user_access/room_invite.md). В противном случае доступ к этой комнате будет иметь только её создатель. ### Активация приглашения[​](#активация-приглашения "Direct link to Активация приглашения") Активация можно произвести двумя способами: * Сгенерировать ссылку и отправить пользователю для его самостоятельной активации. Такая ссылка будет иметь вид `https://edu.livedigital.spaceinvite/:spaceId/:inviteId` * [Активировать этот инвайт](/docs/moodhood-api/v1/activate-invite.md) самостоятельно, используя **[токен доступа пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, которому необходимо выдать права к этой комнате. ### Удаление приглашения[​](#удаление-приглашения "Direct link to Удаление приглашения") Для удаления необходимо воспользоваться эндпоинтом `DELETE /spaces/:spaceId/invites/:inviteId` Подробнее [см. в схеме](/docs/moodhood-api/v1/delete-invite.md) info Обратите внимание, что методы которые могли бы показать вам список всех приглашений и их статусов отсутствуют. Т.е. если вы хотите отозвать какое-либо приглашение, то хранить его идентификатор необходимо на вашей стороне. --- # Пользователи Пользователь является основным ресурсом API, от имени которого выполняются запросы. Пользователь идентифицируется при помощи полученного Bearer [токена доступа](/docs/tutorials/auth/get_tokens.md), который указывается в Authorization заголовке запроса. ### Регистрация пользователя[​](#регистрация-пользователя "Direct link to Регистрация пользователя") info Для регистрации пользователя вам понадобится Bearer токен доступа **[клиента](/docs/tutorials/auth/create_oauth_client.md)**, указанный в заголовке Authorization Для регистрации необходимо выполнить запрос к [эндпоинту создания пользователя](/docs/moodhood-api/v1/create-new-user.md). POST `https://moodhood-api.livedigital.space/v1/users` С телом: ``` { "email": "some-email@example.com", "password": "some_password", "username": "Some Username Here", "phone": "+79999999999", } ``` tip Подробнее о схеме запроса и ответа можно посмотреть [здесь](/docs/moodhood-api/v1/create-new-user.md) Обязательными являются поля **email** и **password** В ответе вам вернётся уникальный ID вашего пользователя, который вы можете сохранить у себя и смаппить с пользователем в вашей системе. ### Обновление пользователя[​](#обновление-пользователя "Direct link to Обновление пользователя") info Для всех дальнейших действий с пользователем вам понадобится токен доступа **[пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0)**, указанный в заголовке Authorization Для регистрации необходимо выполнить запрос к [эндпоинту обновления пользователя](/docs/moodhood-api/v1/create-new-user.md). PUT `https://moodhood-api.livedigital.space/v1/users/me` Возможные поля для изменения указаны в [спецификации запроса](/docs/moodhood-api/v1/update-user.md) ### Смена учётных данных[​](#смена-учётных-данных "Direct link to Смена учётных данных") Смена пароля или email пользователя осуществляется в два этапа: 1. Отправка запроса и получение ссылки на изменение 2. Активация ссылки Соответствующие запросы можно найти в спецификации: * [Отправка запроса на смену email](/docs/moodhood-api/v1/request-email-change.md) * [Отправка запроса на смену пароля](/docs/moodhood-api/v1/request-password-change.md) * [Активация ссылки на смену email](/docs/moodhood-api/v1/update-user-email.md) * [Активация ссылки на смену пароля](/docs/moodhood-api/v1/update-user-password.md) note Обратите внимание, что все письма приходят на текущий email пользователя, а для смены пароля необходимо [отправить в теле запроса](/docs/moodhood-api/v1/request-password-change.md) его текущий пароль и токен подтверждения, отправленный в письме. tip Проверьте папку **spam** при ожидании письма, некоторые почтовые сервисы могут распознавать рассылку как спам. ### Удаление пользователя[​](#удаление-пользователя "Direct link to Удаление пользователя") DELETE `https://moodhood-api.livedigital.space/v1/users/:userId` Подробнее можно посмотреть [здесь](/docs/moodhood-api/v1/delete-user.md) info При отправке запроса на удаление пользователя необходимо указать его текущий пароль --- # Веб хуки API ВКС предоставляет возможность отслеживать события при помощи механизма вебхуков (webhook). Это такой механизм отправки обратных запросов с уведомлением при наступлении в системе определенного события, благодаря чему на такие события можно подписаться. Пример события, для которого можно создать вебхук и подписаться - начало или окончание звонка. При наступлении события сервисом ВКС выполнится POST-запрос на заранее заданный URL. Интегратору на своей стороне следует реализовать обработку запроса. Например, можно реализовать отправку уведомлений администратору группы. При этом следует иметь в виду, что доставка уведомлений не гарантирована. Если POST-запрос не был обработан, его отправят снова с определенным интервалом. Всего сделают 3 попытки. Если запрос трижды не обработали на стороне интегратора, он больше не отправляется. info Всего отправляется 3 ретрая с следующей периодичностью: 1. Первая попытка: Исходный запрос. 2. Вторая попытка: Происходит через 6 секунд после того, как завершилась предыдущая (неудачная) попытка. 3. Третья попытка: Происходит через 9 секунд после того, как завершилась вторая неудачная попытка. Тайм-аут запроса 10 секунд. warning На каждую группу (спэйс) вебхук настраивается отдельно. То есть, если у вас 10 групп, то необходимо настроить 10 вебхуков. При создании вебхука возвращаются данные следующего вида: ``` { "url": "https://some.url", "secret": "g7r0gOSKiQ59prCGXo1H50PACb5ca1mD", "isActive": true } ``` Здесь url это адрес, по которому будет выполнен обратный POST-запрос сервисом ВКС. Значение secret - ключ для проверки валидности созданного вебхука (см. описание ниже). isActive - признак того, что вебхук активен. В результате наступления события сервис ВКС отправит на указанный URL запрос, передав в теле запроса данные следующего вида (формат данных в `body` может отличаться в зависимости от типа события `eventName`): ``` { "signature": "string", "body": { "eventName": "payload.eventName", "roomId": "payload.roomId", "spaceId": "payload.spaceId", "recordId": "payload.recordId" }, } ``` В блоке body содержатся данные о событии: название события eventName и идентификаторы комнаты\группы, для которых наступило событие. #### Возможные значения eventName:[​](#возможные-значения-eventname "Direct link to Возможные значения eventName:") * **call\_started** — звонок начался; * **call\_finished** — звонок закончился; * **record\_started** — начало облачной записи; * **record\_finished** — окончание облачной записи (успешно); * **record\_aborted** — запись была остановлена пользователем в течении 3-х секунд после старта; * **record\_failed** — во время облачной записи произошла ошибка; * **transcribation\_finished** — транскрипция облачной записи успешно сформирована (в т.ч. саммари); * **transcribation\_failed** — во время формирования транскрипции облачной записи произошла ошибка. Также возвращается строковое значение signature. Это сигнатура для данных, содержащихся в body, сформированная по алгоритму HMAC sha256, а в качестве ключа было использовано значение secret (см. выше). Пользователю, создавшему вебхук, известно значение secret. Поэтому можно вычислить сигнатуру для данных в body и сравнить со значением, вернувшимся в signature. При совпадении сигнатур данные считаются валидными. При несовпадении сигнатур следует отклонить запрос, так как данные были скомпрометированы и не соответствуют действительности. Пример кода на NodeJS для вычисления сигнатуры: ``` crypto.createHmac('sha256', secret).update(JSON.stringify(body), 'utf8').digest('hex'); ``` Для работы с вебхуками API ВКС предоставляет указанные ниже методы. info Важно! При создании вебхуков для авторизации используется [токен пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0) — владельца группы (спэйса). ### Создание вебхука[​](#создание-вебхука "Direct link to Создание вебхука") Для создания вебхука используется запрос createWebhook. В качестве параметра в строке запроса указывается идентификатор группы (см. [Создание группы](/docs/tutorials/spaces.md)), а в теле запроса передается параметр URL - адрес, по которому должен быть выполнен обратный запрос с уведомлением о наступившем событии. Полная спецификация метода приведена [см. здесь](/docs/moodhood-api/v1/create-web-hook.md). Пример выполнения запроса [createWebhook](/docs/moodhood-api/v1/create-web-hook.md) POST `https://moodhood-api.livedigital.space/v1/spaces/spaceId/webhook` где **60d55c0eb9ef88ab17b0aabb** - идентификатор группы. ``` { "url": "https://some.url/" } ``` Пример кода для cURL: ``` curl --location --request POST \ 'https://moodhood-api.livedigital.space/v1/spaces/62bcc725721aeb718445daf7/webhook' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.*****.5vTOaqS77Yl-4cYT1WM5DdKbo8-IybvbxB2DX5kyFaTA' \ --header 'Content-Type: text/plain' \ --data-raw '{ "url": "https://some.url/", }' ``` В результате выполнения запроса будет создан вебхук для указанной группы и вернется JSON с параметрами: url - адрес, по которому будет выполнен обратный запрос, seret - ключ для проверки подлинности данных, возвращаемых с помощью созданного вебхука, isActive - признак активности вебхука (по умолчанию true). Результат: ``` { "url": "https://some.url", "secret": "g7r0gOSKiQ59prCGXo1H50PACb5ca1mD", "isActive": true } ``` Создать вебхук можно также в личном кабинете в разделе «Интеграция»: для этого следует ввести значение URL в поле «WebHook URL» и нажать кнопку «Сохранить». Значение сгенерированного ключа отобразится в поле «WebHook secret», откуда его можно скопировать. В случае компрометации ключа его следует поменять, нажав кнопку «Обновить». ![Меню интеграция](/assets/images/webhooks_settings-d73e7c72d39562fb1b0acd0a16cfb224.png) ### Изменение параметров вебхука[​](#изменение-параметров-вебхука "Direct link to Изменение параметров вебхука") Для созданного ранее вебхука можно изменить значение isActive - признак того, что вебхук активен. То есть можно заблокировать или активировать вебхук. Для этого используется запрос updateWebhook. Запрос похож на создание вебхука, только используется тип запроса PUT. В качестве параметра в строке запроса указывается идентификатор группы (см. Создание группы), а в теле запроса передаются параметры: * **url** - адрес, по которому выполняется обратный запрос с уведомлением о событии. * **isActive** - признак активности вебхука, true - вебхук активен или false - вебхук заблокирован. Полная спецификация метода приведена [здесь](/docs/moodhood-api/v1/update-web-hook.md). Пример выполнения запроса [updateWebhook](/docs/moodhood-api/v1/update-web-hook.md) `PUT /spaces/:spaceId/webhook` info Важно! При изменении и создании вебхуков для авторизации используется персональный токен или [токен пользователя](/docs/tutorials/auth/get_tokens.md#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%BE%D0%B3%D0%BE-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%B0-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0) — владельца группы (спэйса). ``` { "url": "https://some.url/", "isActive": true } ``` Пример кода для cURL: ``` curl --location --request PUT \ 'https://moodhood-api.livedigital.space/v1/spaces/62bcc725721aeb718445daf7/webhook' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.*****.5vTOaqS77Yl-4cYT1WM5DdKbo8-IybvbxB2DX5kyFaTA' \ --header 'Content-Type: text/plain' \ --data-raw '{ "url": "https://some.url/", "isActive": true }' ``` В результате выполнения запроса статус активности вебхука для указанной группы обновится и вернется JSON с параметрами: * url - адрес, по которому должен быть выполнен обратный запрос, * isActive - установленное значение активности вебхука. Результат: ``` { "url": "https://some.url", "isActive": true } ``` ### Получение данных созданного вебхука[​](#получение-данных-созданного-вебхука "Direct link to Получение данных созданного вебхука") Чтобы получить данные созданного ранее вебхука, используется метод [getWebhook](/docs/moodhood-api/v1/get-web-hook.md). Запрос похож на создание вебхука, только используется тип запроса GET. В качестве параметра в строке запроса указывается идентификатор группы ([см. Создание группы](/docs/tutorials/spaces.md)). Пример выполнения запроса [getWebhook](/docs/moodhood-api/v1/get-web-hook.md): `GET /spaces/:spaceId/webhook` Пример кода для cURL: ``` curl --location --request GET \ 'https://moodhood-api.livedigital.space/v1/spaces/62bcc725721aeb718445daf7/webhook' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.*****.5vTOaqS77Yl-4cYT1WM5DdKbo8-IybvbxB2DX5kyFaTA' \ --header 'Content-Type: text/plain' \ --data-raw '{ "url": "https://some.url/", "isActive": true }' ``` В результате выполнения запроса возвращаются данные вебхука для указанной группы: * **url** - адрес, по которому должен быть выполнен обратный запрос, * **isActive** - установленное значение активности вебхука. Результат: ``` { "url": "https://some.url", "isActive": true } ``` ### Удаление вебхука[​](#удаление-вебхука "Direct link to Удаление вебхука") Если нужно удалить вебхук для группы, то используется метод deleteWebhook. Выполняется запрос типа DELETE. В качестве параметра в строке запроса указывается идентификатор группы ([см. Создание группы](/docs/tutorials/spaces.md)). Пример выполнения запроса [deleteWebhook](/docs/moodhood-api/v1/delete-web-hook.md): `DELETE /spaces/:spaceId/webhook` Пример кода для cURL: ``` curl --location --request DELETE \ 'https://moodhood-api.livedigital.space/v1/spaces/62bcc725721aeb718445daf7/webhook' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.******.5vTOaqS77Yl-4cYT1WM5DdKbo8-IybvbxB2DX5kyFaTA' \ --header 'Content-Type: text/plain' ``` В результате выполнения запроса вебхук для заданной группы будет удален. ### Обновление ключа для проверки валидности вебхука[​](#обновление-ключа-для-проверки-валидности-вебхука "Direct link to Обновление ключа для проверки валидности вебхука") Если в ходе проверки валидности вебхука выяснилось, что сигнатуры не совпадают, и есть подозрение, что ключ скомпрометирован, то его можно обновить. Для этого используется метод refreshSecretWebhook. В качестве параметра в строке запроса указывается идентификатор группы ([см. Создание группы](/docs/tutorials/spaces.md)), для которого нужно обновить ключ. Запрос выполняется по методу PUT. Пример выполнения запроса [refreshSecretWebhook](/docs/moodhood-api/v1/refresh-secret-web-hook.md): PUT /spaces/:spaceId/webhook/refresh-secret Пример кода для cURL ``` curl --location --request PUT \ 'https://moodhood-api.livedigital.space/v1/spaces/62bcc725721aeb718445daf7/webhook/refresh-secret' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.*****.5vTOaqS77Yl-4cYT1WM5DdKbo8-IybvbxB2DX5kyFaTA' \ --header 'Content-Type: text/plain' ``` В результате выполнения запроса вернутся данные вебхука с новым значением secret: ``` { "url": "https://some.url", "secret": "g7r0gOSKiQ59prCGXo1H50PACb5ca1mD", "isActive": true } ``` --- # Метаданные участника (appData) `appData` — это JSON-объект, который каждый участник несёт с собой в канале. Все остальные участники видят его в реальном времени и получают событие `app-data-updated` при каждом изменении. **Ограничение:** максимальный размер — **1 КБ** (после JSON-сериализации). ## Когда appData обновляется[​](#когда-appdata-обновляется "Direct link to Когда appData обновляется") 1. При входе в канал — передаётся в `client.join({ appData: { ... } })` 2. В любой момент сессии — через `client.updateAppData({ ... })` 3. Другие участники получают `app-data-updated` немедленно ``` // При входе await client.join({ token, appData: { displayName: 'Иван Петров', avatarUrl: 'https://example.com/avatar.jpg', }, }); // В процессе звонка — полный объект, не patch await client.updateAppData({ displayName: 'Иван Петров', avatarUrl: 'https://example.com/avatar.jpg', isHandRaised: true, // добавили новое поле }); // Подписчики получат обновлённые данные peer.observer.on('app-data-updated', (data) => { console.log(data); // весь объект, не только изменённые поля }); ``` updateAppData заменяет весь объект `updateAppData` выполняет полную замену, а не patch. Если передать только одно поле — остальные будут удалены. Всегда мёрджите с текущими данными: ``` const current = getCurrentAppData(); // сохраняйте состояние у себя await client.updateAppData({ ...current, isHandRaised: true }); ``` ## Примеры использования[​](#примеры-использования "Direct link to Примеры использования") ### Отображение имени и аватара[​](#отображение-имени-и-аватара "Direct link to Отображение имени и аватара") ``` await client.join({ token, appData: { displayName: 'Иван Петров', avatarUrl: 'https://cdn.example.com/users/123/avatar.jpg', }, }); peer.observer.on('app-data-updated', (data) => { updateNameLabel(peer.id, data.displayName as string); updateAvatar(peer.id, data.avatarUrl as string); }); ``` ### Поднятие руки[​](#поднятие-руки "Direct link to Поднятие руки") ``` let appData = { displayName: 'Иван Петров' }; async function raiseHand() { appData = { ...appData, isHandRaised: true, raisedAt: Date.now() }; await client.updateAppData(appData); } async function lowerHand() { appData = { ...appData, isHandRaised: false, raisedAt: null }; await client.updateAppData(appData); } // Ведущий видит поднятые руки peer.observer.on('app-data-updated', (data) => { if (data.isHandRaised) { addToRaiseHandQueue(peer.id, data.raisedAt as number); } else { removeFromQueue(peer.id); } }); ``` ### Статус медиаустройств[​](#статус-медиаустройств "Direct link to Статус медиаустройств") Полезно, когда нужно показывать иконки камеры/микрофона в UI других участников: ``` // Обновлять при каждом включении/выключении async function onCameraToggle(isEnabled: boolean) { appData = { ...appData, isCameraOn: isEnabled }; await client.updateAppData(appData); } async function onMicToggle(isEnabled: boolean) { appData = { ...appData, isMicOn: isEnabled }; await client.updateAppData(appData); } // В UI остальных участников peer.observer.on('app-data-updated', (data) => { updateCameraIcon(peer.id, data.isCameraOn as boolean); updateMicIcon(peer.id, data.isMicOn as boolean); }); ``` ### Реакции (эмодзи)[​](#реакции-эмодзи "Direct link to Реакции (эмодзи)") ``` async function sendReaction(emoji: string) { appData = { ...appData, reaction: emoji, reactionAt: Date.now() }; await client.updateAppData(appData); // Сбросить через 3 секунды setTimeout(async () => { appData = { ...appData, reaction: null }; await client.updateAppData(appData); }, 3000); } peer.observer.on('app-data-updated', (data) => { if (data.reaction) { showReactionBubble(peer.id, data.reaction as string); } }); ``` ### Роль в конференции[​](#роль-в-конференции "Direct link to Роль в конференции") Если в вашем приложении есть собственные роли (не только SDK-роли `host`/`audience`): ``` await client.join({ token, appData: { displayName: 'Иван Петров', appRole: 'teacher', // ваша прикладная роль }, }); peer.observer.on('app-data-updated', (data) => { if (data.appRole === 'teacher') { showTeacherBadge(peer.id); } }); ``` ### Качество исходящего соединения[​](#качество-исходящего-соединения "Direct link to Качество исходящего соединения") Можно сообщать другим участникам о плохом соединении: ``` const client = new Client({ onNetworkScoresUpdated: async (scores) => { const quality = scores.outbound < 0.3 ? 'bad' : scores.outbound < 0.7 ? 'poor' : 'good'; appData = { ...appData, connectionQuality: quality }; await client.updateAppData(appData); }, }); // Другие участники показывают иконку соединения peer.observer.on('app-data-updated', (data) => { updateConnectionBadge(peer.id, data.connectionQuality as string); }); ``` ## Чтение appData при входе[​](#чтение-appdata-при-входе "Direct link to Чтение appData при входе") Участники, которые вошли до вас, уже имеют `appData` — оно доступно сразу после `channel-state-synced`: ``` client.observer.on('channel-state-synced', () => { for (const peer of client.peers) { if (peer.isMe) continue; // appData уже заполнено renderPeerCard(peer.id, peer.appData); } }); ``` --- # Управление полосой пропускания В конференции с большим числом участников не нужно подписываться на все медиапотоки сразу. Управление подписками — основной инструмент снижения нагрузки на сеть и браузер. ## Стратегия подписки по видимости[​](#стратегия-подписки-по-видимости "Direct link to Стратегия подписки по видимости") Подписывайтесь только на видеотреки участников, которые видны на экране. При скрытии участника — отписывайтесь. ``` // Подписаться на видео участника (он появился в viewport) async function onPeerVisible(peer: Peer) { const cameraPublisher = peer.publishedMedia?.find(m => m.label === 'camera'); if (cameraPublisher) { await peer.subscribe({ producerId: cameraPublisher.producerId }); } } // Отписаться от видео участника (он вышел из viewport) async function onPeerHidden(peer: Peer) { const cameraPublisher = peer.publishedMedia?.find(m => m.label === 'camera'); if (cameraPublisher) { await peer.unsubscribe(cameraPublisher.producerId); } } ``` Аудио не трогайте Отписывайтесь только от **видеотреков**. Аудио — подписывайте всегда, независимо от видимости участника: пользователь должен слышать всех. ## Адаптивное качество видео (Simulcast / SVC)[​](#адаптивное-качество-видео-simulcast--svc "Direct link to Адаптивное качество видео (Simulcast / SVC)") SDK поддерживает simulcast — публикацию нескольких слоёв видео с разным качеством. Подписчик может запросить нужный слой в зависимости от размера слота в интерфейсе. ``` // Слои simulcast: // 0 — низкое качество (маленький слот, плохая сеть) // 1 — среднее качество // 2 — высокое качество (большой слот, хорошая сеть) // Установить максимальный слой для локального трека // (ограничивает исходящее качество) await localVideoTrack.setMaxSpatialLayer(1); // максимум — средний слой // Для входящих треков — управляется на уровне consumer'а (автоматически) ``` **Когда менять слой:** * Переключение на маленькую плитку в галерее → `setMaxSpatialLayer(0)` * Выход на большой экран/spotlight → `setMaxSpatialLayer(2)` * Плохое соединение → `setMaxSpatialLayer(0)` ## Отключение видео при плохой сети[​](#отключение-видео-при-плохой-сети "Direct link to Отключение видео при плохой сети") ``` let videoAutoDisabled = false; const client = new Client({ onNetworkScoresUpdated: async (scores) => { if (scores.inbound < 0.2 && !videoAutoDisabled) { // Сеть очень плохая — отписаться от всех видеотреков videoAutoDisabled = true; for (const peer of client.peers) { if (peer.isMe) continue; for (const media of peer.publishedMedia) { if (media.kind === 'video') { await peer.unsubscribe(media.producerId); } } } showBanner('Видео отключено из-за плохого соединения'); } if (scores.inbound > 0.5 && videoAutoDisabled) { // Сеть восстановилась — переподписаться videoAutoDisabled = false; await resubscribeVisibleVideos(); } }, }); ``` ## Рекомендации по числу участников[​](#рекомендации-по-числу-участников "Direct link to Рекомендации по числу участников") | Участников | Рекомендация | | ------------------ | ---------------------------------------------------------------------------------- | | До 4 | Подписываться на всех, слой 2 | | 5–12 | Подписываться на видимых, слой 1 для маленьких слотов | | 12–50 | Подписываться только на видимых в viewport, слой 0–1 | | 50+ | Подписываться только на активного спикера + 3–5 рядом | | Вебинар (audience) | Загружать только `host`-пиров, см. [Режим вебинара](/docs/web-sdk/webinar-mode.md) | Если участников больше 50, лучше не пытаться подписаться на всех — это перегрузит браузер. Подписывайтесь только на активного спикера и пару соседних участников. В случае наличия демонстрации экрана — её стоит приоритезировать, даже если спикер неактивен. Для остальных видео следует использовать слой 0 (низкое качество). --- # Инициализация Client `Client` — главный класс SDK. Создаётся один раз при старте приложения и управляет всем жизненным циклом: подключением к каналу, медиа-треками, участниками и сетевым транспортом. Singleton Кешируйте один `Client` на всё время жизни страницы. Повторный `new Client()` в том же JS-контексте регистрирует DI-bindings ещё раз и падает с `Ambiguous match found for serviceIdentifier: ClientEventEmitter`. На disconnect очищайте listeners, но не пересоздавайте клиент. ## Минимальная конфигурация[​](#минимальная-конфигурация "Direct link to Минимальная конфигурация") ``` import { Client } from '@livedigital/client'; const client = new Client(); ``` ## Production-конфигурация[​](#production-конфигурация "Direct link to Production-конфигурация") ``` const client = new Client({ staticFilesPath: 'https://cdn.example.com/static', logLevel: 4, sendAnalytics: true, getStatsInterval: 2000, onNetworkScoresUpdated: (scores) => { console.log('Качество сети:', scores); }, onIssues: (issues) => { console.warn('Проблемы WebRTC:', issues); }, }); ``` ## Основные параметры[​](#основные-параметры "Direct link to Основные параметры") | Параметр | Тип | Описание | | -------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `signalingServerUrl` | `string` | URL сигнального сервера livedigital | | `staticFilesPath` | `string` | Базовый URL статических файлов SDK (rnnoise, ESDK/ASDK). По умолчанию `window.location.origin`; для CDN используйте `https://static.prod.livedigital.space` | | `logLevel` | `3 \| 4 \| 6 \| 7` | Уровень логирования: 3 — error, 4 — warn, 6 — info, 7 — debug | | `sendAnalytics` | `boolean` | Отправлять аналитику звонков | | `getStatsInterval` | `number` | Интервал сбора WebRTC-статистики в мс (по умолчанию 1000) | | `disableWid` | `boolean` | Отключить встроенный детектор проблем WebRTC | ## Колбэки и диагностика[​](#колбэки-и-диагностика "Direct link to Колбэки и диагностика") | Параметр | Тип | Описание | | ------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------- | | `onLogMessage` | `(msg, ...meta) => void` | Перехват лог-сообщений SDK | | `onIssues` | [`IssuesHandler`](/docs/sdk-api/type-aliases/IssuesHandler.md) | Уведомления о проблемах WebRTC-соединения | | `onNetworkScoresUpdated` | [`NetworkScoresUpdatedHandler`](/docs/sdk-api/type-aliases/NetworkScoresUpdatedHandler.md) | Обновление оценки качества сети | ## Параметры эффектов и шумоподавления[​](#параметры-эффектов-и-шумоподавления "Direct link to Параметры эффектов и шумоподавления") ``` const client = new Client({ // Шумоподавление denoiser: 'asdk', // 'rnnoise' (по умолчанию) или 'asdk' asdk: { version: '2.3.5', localDir: '/static/asdk', }, // Видеоэффекты ESDK effectsSDKParams: { version: '3.8.0', localDir: '/static/esdk', }, }); ``` Полные параметры эффектов — [`InitEffectsSDKParams`](/docs/sdk-api/interfaces/InitEffectsSDKParams.md) в справочнике API. ## Логирование[​](#логирование "Direct link to Логирование") SDK использует пакет `debug`. Подробные логи можно включить в браузере: ``` // Включить все логи SDK localStorage.setItem('debug', 'LiveDigital:*'); // Или конкретный модуль localStorage.setItem('debug', 'LiveDigital:Engine'); // Перехват через конструктор const client = new Client({ logLevel: 7, onLogMessage: (message, ...meta) => { myLogger.debug(message, meta); }, }); ``` ## Полный список параметров[​](#полный-список-параметров "Direct link to Полный список параметров") Полная типизация — [`ClientParams`](/docs/sdk-api/interfaces/ClientParams.md) в справочнике API. --- # Подключение к комнате Канал (room/channel) — пространство, в котором участники обмениваются медиапотоками. SDK поддерживает роли участников, автоматическое восстановление соединения и ручное переподключение. ## Подключение и синхронизация[​](#подключение-и-синхронизация "Direct link to Подключение и синхронизация") Правильный порядок: подписки на события → `join()` → `requestChannelStateSync()` → обработка `channel-state-synced`. ``` // Подписки — до join(), чтобы не пропустить ни одного события client.observer.on('peer-joined', (peer) => { /* ... */ }); client.observer.on('peer-left', (peerId) => { /* ... */ }); // channel-state-synced: данные звонка синхронизированы с сервером, // пиры загружены — только теперь можно работать с уже присутствующими client.observer.on('channel-state-synced', () => { console.log('Синхронизировано. Участники:', client.peers); // подписаться на треки уже присутствующих (catch-up) }); await client.join({ token: 'your-signaling-token', appData: { displayName: 'Иван Петров', avatarUrl: 'https://example.com/avatar.jpg', }, }); // Запросить полный снимок состояния канала client.requestChannelStateSync(); ``` requestChannelStateSync() обязателен после join() Без явного вызова `requestChannelStateSync()` данные канала не синхронизируются. Событие `channel-state-synced` — единственная гарантия того, что `client.peers` содержит актуальный список участников с их медиапотоками. Выполнять любые действия с пирами до этого события небезопасно. ## Параметры join()[​](#параметры-join "Direct link to Параметры join()") | Параметр | Тип | Описание | | ----------- | ------------------------- | ----------------------------------------------------------- | | `token` | `string` | Временный signaling-token. Не Personal token и не jwtSecret | | `appData` | `Record` | JSON-serializable метаданные участника | | `isP2pCall` | `boolean` | P2P-режим для звонка 1-на-1 | Полные параметры — [`JoinChannelParams`](/docs/sdk-api/interfaces/JoinChannelParams.md). Канал и роль участника определяются сервером на основе `signalingToken`. Как получить `token` — смотрите [Подготовка комнаты](/docs/web-sdk/room-setup.md). При **on-premise** развёртывании токен можно [сгенерировать самостоятельно](/docs/tutorials/user_access/generate_signaling_token.md), без обращения к Moodhood API. ## Переподключение и принудительное отключение[​](#переподключение-и-принудительное-отключение "Direct link to Переподключение и принудительное отключение") SDK автоматически восстанавливает соединение при кратковременных потерях сети. Для ручного управления: ``` client.observer.on('connection-lost', () => { showNotification('Соединение потеряно, восстанавливаем...'); }); client.observer.on('connection-restored', () => { showNotification('Соединение восстановлено'); }); // При channel-rejoin-required всегда запрашивайте новый signalingToken у бэкенда client.observer.on('channel-rejoin-required', async () => { const { token } = await fetchNewToken(); await client.join({ token }); client.requestChannelStateSync(); }); // Серверное принудительное отключение (например, кик модератором) client.observer.on('forced-disconnect', () => { navigateToHome(); }); // Токен недействителен client.observer.on('reject-unauthorized', () => { refreshTokenAndRejoin(); }); ``` ## Подтверждение активности[​](#подтверждение-активности "Direct link to Подтверждение активности") Сервер периодически проверяет, есть ли в канале живые участники. Событие `channel-activity-confirmation-required` отправляется **всем участникам канала одновременно**. Если никто из них не вызовет `confirmActivity()` до истечения таймаута — канал закрывается целиком и все отключаются. ``` client.observer.on('channel-activity-confirmation-required', async ({ time }) => { const timeoutSeconds = Math.floor(time / 1000) - 1; const confirmed = await askUserConfirmation(timeoutSeconds); // показать диалог с таймером if (confirmed) { await client.confirmActivity(); } else { await client.leave(); } }); ``` ## Отключение[​](#отключение "Direct link to Отключение") ``` await client.leave(); // покинуть и освободить ресурсы await client.leave(true); // покинуть, но сохранить локальные треки ``` При `keepTracks: true` локальные треки (камера, микрофон) не останавливаются и не удаляются — они остаются доступны для повторной публикации после следующего `join()`. Полезно при быстром переходе между комнатами, когда не нужно заново запрашивать доступ к устройствам. ## Методы управления сессией[​](#методы-управления-сессией "Direct link to Методы управления сессией") | Метод | Описание | | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | [`client.join(params)`](/docs/sdk-api/classes/Client.md) | Войти в канал | | [`client.leave(keepTracks?)`](/docs/sdk-api/classes/Client.md) | Покинуть канал | | [`client.requestChannelStateSync()`](/docs/sdk-api/classes/Client.md) | Запросить синхронизацию состояния канала | | [`client.confirmActivity()`](/docs/sdk-api/classes/Client.md) | Подтвердить активность участника (ответ на `channel-activity-confirmation-required`) | | [`client.updateAppData(appData)`](/docs/sdk-api/classes/Client.md) | Обновить метаданные участника в реальном времени | ## Связанные события[​](#связанные-события "Direct link to Связанные события") Полный список событий подключения — в разделе [Справочник событий](/docs/web-sdk/events-reference.md). --- # Управление устройствами SDK предоставляет API для обнаружения камер и микрофонов, отслеживания их подключения/отключения и выбора конкретного устройства при создании трека. ## Обнаружение устройств[​](#обнаружение-устройств "Direct link to Обнаружение устройств") ``` const devices = await client.detectDevices(); console.log('Камеры:', devices.video); console.log('Микрофоны:', devices.audio); // Создание трека с конкретным устройством const videoTrack = await client.createCameraVideoTrack({ video: { deviceId: { exact: devices.video[1].deviceId } }, }); ``` Возвращает [`AvailableMediaDevices`](/docs/sdk-api/interfaces/AvailableMediaDevices.md). detectDevices() запрашивает разрешение При первом вызове браузер показывает запрос доступа к камере/микрофону. Без этого разрешения `device.label` будет пустым (политика безопасности браузеров). ## Отслеживание изменений списка устройств[​](#отслеживание-изменений-списка-устройств "Direct link to Отслеживание изменений списка устройств") ``` client.observer.on('devices-list-updated', (updated) => { rebuildDeviceSelectors(updated); }); ``` Событие `devices-list-updated` приходит при подключении или отключении устройства. ## Переключение устройства во время звонка[​](#переключение-устройства-во-время-звонка "Direct link to Переключение устройства во время звонка") Используйте `replaceTrack()` — это меняет источник без обрыва WebRTC-стрима. Остальные участники не увидят «чёрного экрана» при переключении камеры. ``` async function switchCamera(deviceId: string) { const newStream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } }, }); await videoTrack.replaceTrack(newStream.getVideoTracks()[0]); } async function switchMicrophone(deviceId: string) { const newStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: deviceId } }, }); await audioTrack.replaceTrack(newStream.getAudioTracks()[0]); } ``` ## Управление разрешениями участников на устройства[​](#управление-разрешениями-участников-на-устройства "Direct link to Управление разрешениями участников на устройства") Модератор может ограничить использование камеры, микрофона и демонстрации экрана на стороне сервера. Подробнее — [Управление устройствами пользователя](/docs/tutorials/user_access/device_restrictions.md). --- # Обработка ошибок ## Ошибки join()[​](#ошибки-join "Direct link to Ошибки join()") `client.join()` может выбросить исключение в двух случаях: сетевая ошибка при подключении к сигнальному серверу и недействительный/просроченный `signalingToken`. Всегда оборачивайте вызов в `try/catch`: ``` try { await client.join({ token }); client.requestChannelStateSync(); } catch (error) { if (error instanceof Error) { if (error.message.includes('authentication failed')) { // Токен недействителен — получите новый signalingToken showError('Ошибка авторизации. Попробуйте войти снова.'); } else { showError('Не удалось подключиться к комнате.'); } } } ``` signalingToken живёт 24 часа Токен, выпущенный через API `/signaling-token`, действует 24 часа. При получении события `channel-rejoin-required` рекомендуется запросить свежий токен у бэкенда — это повышает надёжность переподключения. В on-premise сценариях, когда токен [выпускается своим бэкендом](/docs/tutorials/user_access/generate_signaling_token.md), время жизни вы задаёте сами в поле `exp`. Помимо `throw`, SDK может сигнализировать об ошибках авторизации событием: ``` client.observer.on('reject-unauthorized', () => { // Токен отклонён уже после join() — редкий сценарий showError('Сессия истекла. Войдите снова.'); navigateToLogin(); }); ``` ## Ошибки доступа к устройствам[​](#ошибки-доступа-к-устройствам "Direct link to Ошибки доступа к устройствам") ### Отказ в доступе (NotAllowedError)[​](#отказ-в-доступе-notallowederror "Direct link to Отказ в доступе (NotAllowedError)") ``` try { const videoTrack = await client.createCameraVideoTrack(); } catch (error) { if (error instanceof DOMException) { if (error.name === 'NotAllowedError') { showPermissionsDeniedBanner(); } else if (error.name === 'NotFoundError') { showNoCameraFoundBanner(); } else if (error.name === 'NotReadableError') { // Камера занята другим приложением showCameraInUseError(); } } } ``` ### Физическое отключение устройства во время звонка[​](#физическое-отключение-устройства-во-время-звонка "Direct link to Физическое отключение устройства во время звонка") Браузер генерирует событие `ended` на `MediaStreamTrack` при физическом отключении устройства: ``` cameraTrack.mediaStreamTrack.addEventListener('ended', async () => { // Камера физически отключена (USB, переключение вкладок на iOS, etc.) try { await client.deleteTrack(cameraTrack); cameraTrack = undefined; isVideoDisabled = true; // Обновить UI showCameraOffPlaceholder(); // Можно попробовать пересоздать трек с другой камерой await tryFallbackCamera(); } catch (err) { console.error('Не удалось обработать отключение камеры', err); } }, { once: true }); ``` ### Клиентский track-failed[​](#клиентский-track-failed "Direct link to Клиентский track-failed") ``` client.observer.on('track-failed', ({ label }) => { console.warn(`Трек ${label} завершился с ошибкой`); if (label === 'camera') { cameraTrack = undefined; showCameraError('Камера неожиданно отключилась'); } else if (label === 'microphone') { micTrack = undefined; showMicError('Микрофон неожиданно отключился'); } }); ``` ## Ошибки воспроизведения (autoplay)[​](#ошибки-воспроизведения-autoplay "Direct link to Ошибки воспроизведения (autoplay)") ``` peer.observer.on('track-start', async (track) => { if (track.kind !== 'video') return; const videoEl = getVideoElement(peer.id); videoEl.srcObject = new MediaStream([track.mediaStreamTrack]); try { await videoEl.play(); } catch (e) { if (e instanceof DOMException && e.name === 'NotAllowedError') { // Браузер заблокировал автовоспроизведение // Показать кнопку "Нажмите для включения звука" showUnmuteButton(() => videoEl.play()); } } }); ``` ## Ошибки ESDK / шумоподавления[​](#ошибки-esdk--шумоподавления "Direct link to Ошибки ESDK / шумоподавления") ``` client.observer.on('effects-failed', ({ cause, operation }) => { console.warn(`ESDK ошибка [${operation}]: ${cause}`); // Отключить эффекты и продолжить без них showToast('Видеоэффекты временно недоступны'); }); client.observer.on('denoiser-failed', ({ cause }) => { console.warn('Шумоподавление недоступно:', cause); // SDK продолжает работу без шумоподавления }); ``` ## Справочная таблица ошибок[​](#справочная-таблица-ошибок "Direct link to Справочная таблица ошибок") | Ситуация | Сигнал | Рекомендованная реакция | | ------------------------------ | -------------------------------------------- | -------------------------------------------------------------------------- | | Неверный токен при join | `throw` в `join()` | Получить новый токен, повторить | | Токен отклонён во время сессии | `reject-unauthorized` | Перевыпустить токен, rejoin | | Сеть пропала | `connection-lost` | Показать баннер, ждать `connection-restored` | | Требуется rejoin | `channel-rejoin-required` | Получить токен, rejoin с retry | | Кик администратором | `forced-disconnect` | Вывести из комнаты | | ICE/DTLS таймаут | `transport-connection-timeout` | Логика relay (см. [Качество соединения](/docs/web-sdk/network-quality.md)) | | Камера отключена физически | `ended` на `MediaStreamTrack` | Удалить трек, попробовать другое устройство | | Камера отключена модератором | `track-force-closed` (`label: 'camera'`) | Удалить трек, показать уведомление | | Ошибка публикации трека | `track-failed` | Логировать, пересоздать трек | | Нет доступа к камере | `NotAllowedError` в `createCameraVideoTrack` | Показать инструкцию по разрешениям | | Камера занята | `NotReadableError` | Предложить закрыть другие приложения | | ESDK не работает | `effects-failed` | Отключить эффекты, продолжить без них | --- # Справочник событий SDK использует событийную модель на основе `observer`. Все события подписываются через `client.observer.on()` или `peer.observer.on()`. Полная карта событий — [`ClientObserverEvents`](/docs/sdk-api/interfaces/ClientObserverEvents.md), [`PeerObserverEvents`](/docs/sdk-api/interfaces/PeerObserverEvents.md) в справочнике API. ``` // Подписка client.observer.on('event-name', (payload) => { /* ... */ }); client.observer.once('event-name', (payload) => { /* ... */ }); // Отписка const handler = (payload) => { /* ... */ }; client.observer.on('event-name', handler); client.observer.off('event-name', handler); ``` *** ## События Client[​](#события-client "Direct link to События Client") ### peer-joined[​](#peer-joined "Direct link to peer-joined") ``` Payload: Peer ``` Новый участник вошёл в канал. В payload — объект `Peer` с уже заполненными `id`, `role`, `appData`. Только host-пиры требуют подписки на медиапотоки `audience`-участники потребляют медиа, но сами не публикуют. Если ваша логика касается только публикующих участников — фильтруйте по `peer.role`: ``` client.observer.on('peer-joined', (peer) => { if (peer.role !== 'host') return; peer.observer.on('media-published', async ({ producerId }) => { await peer.subscribe({ producerId }); }); }); ``` **Особый случай: мобильный screenshare.** На мобильных устройствах демонстрация экрана запускается как отдельный дочерний процесс, который входит в канал независимым пиром с тем же `participantId`. При обнаружении такого пира нужно скопировать participant info от родительского пира, чтобы отображать правильное имя и аватар в интерфейсе. *** ### peer-left[​](#peer-left "Direct link to peer-left") ``` Payload: string (peerId) ``` Участник покинул канал. Нужно убрать его UI и отписаться от его треков. *** ### channel-event[​](#channel-event "Direct link to channel-event") ``` Payload: ChannelEvent ``` Событие синхронизации данных комнаты между сервером и клиентами. Используется для доставки изменений настроек комнаты (room settings), обновлений прав, системных команд и других прикладных событий реального времени. Тип payload — [`ChannelEvent`](/docs/sdk-api/interfaces/ChannelEvent.md). ``` client.observer.on('channel-event', (event) => { // event содержит type и payload, специфичные для конкретной команды handleRoomDataSyncEvent(event); }); ``` Применение Через `channel-event` приходят, например: изменение настроек комнаты (запрет чата, демонстрации экрана, поднятия руки), системные уведомления, синхронизация состояния записи. Это основной канал для любых данных, которые не являются медиапотоками. *** ### channel-state-synced[​](#channel-state-synced "Direct link to channel-state-synced") ``` Payload: — ``` Данные звонка между SDK и сервером синхронизированы: `client.peers` содержит актуальный список участников со всеми опубликованными медиапотоками. Это точка, с которой можно безопасно начинать работу с пирами. Канонический флоу с кодом — в разделе [Участники → Синхронизация состояния после join()](/docs/web-sdk/peers.md#%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5-join). *** ### channel-state-inconsistent[​](#channel-state-inconsistent "Direct link to channel-state-inconsistent") ``` Payload: { type: string, peerId?: string, producerId?: string } ``` Обнаружено расхождение состояния канала между клиентом и сервером. SDK фиксирует несоответствие (например, пир числится в списке, но его данных нет). Как правило, правильная реакция — повторный вызов `requestChannelStateSync()`. Тип payload — [`ChannelStateInconsistentPayload`](/docs/sdk-api/interfaces/ChannelStateInconsistentPayload.md). *** ### channel-rejoin-required[​](#channel-rejoin-required "Direct link to channel-rejoin-required") ``` Payload: — ``` SDK обнаружил, что соединение с каналом необходимо восстановить через повторный `join()`. Это не то же самое, что `connection-lost` — здесь нет смысла ждать автоматического восстановления. **Рекомендуемая реакция:** запросить новый `signalingToken` и вызвать `client.join()` заново. Рекомендуется реализовать retry-логику с несколькими попытками: ``` client.observer.on('channel-rejoin-required', async () => { let retryCount = 0; while (retryCount < 5) { try { const { token } = await fetchNewSignalingToken(); await client.join({ token }); client.requestChannelStateSync(); return; } catch { retryCount++; await sleep(2000); } } // После 5 неудачных попыток — показать ошибку и уйти из комнаты navigateToError(); }); ``` *** ### connection-lost[​](#connection-lost "Direct link to connection-lost") ``` Payload: — ``` Сетевое соединение потеряно. SDK начинает автоматическое восстановление. Рекомендуется показать пользователю индикатор «Соединение потеряно». *** ### connection-restored[​](#connection-restored "Direct link to connection-restored") ``` Payload: — ``` Соединение восстановлено после `connection-lost`. Пользователь снова подключён. Рекомендуется скрыть индикатор потери соединения и синхронизировать любое состояние, которое могло измениться за время разрыва (например, статус записи). *** ### transport-connection-timeout[​](#transport-connection-timeout "Direct link to transport-connection-timeout") ``` Payload: { reason: 'ice' | 'dtls', direction: 'receive' | 'send' } ``` Таймаут установки WebRTC-транспорта. Причина (`reason`) определяет правильную стратегию реагирования: | Причина | Значение | Рекомендованное действие | | ------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | | `ice` | Не удалось найти путь для медиапотока (NAT, firewall) | Показать предупреждение об антивирусе/VPN или переключиться на relay и rejoin | | `dtls` | DTLS-рукопожатие не завершено (deep packet inspection, корпоративный firewall) | Принудительно включить relay (`setPreferRelay(true)`) и rejoin | ``` client.observer.on('transport-connection-timeout', async ({ reason }) => { if (reason === 'ice') { if (client.getPreferRelay()) { // Relay уже включён, но не помог — значит, блокировка на уровне сети client.setPreferRelay(false); showAntivirusBlocksConnectionWarning(); } else { // Прямое соединение не прошло — попробовать relay client.setPreferRelay(true); await rejoin(); } } if (reason === 'dtls') { if (!client.getPreferRelay()) { // Переключить на relay и переподключиться client.setPreferRelay(true); await rejoin(); } else { // Relay тоже не помог — корпоративный firewall полностью блокирует WebRTC showFirewallBlocksConnectionWarning(); } } }); ``` *** ### forced-disconnect[​](#forced-disconnect "Direct link to forced-disconnect") ``` Payload: — ``` Пользователь принудительно отключён сервером (например, кикнут модератором или администратором). Приложение должно завершить сессию и вывести пользователя из комнаты. *** ### reject-unauthorized[​](#reject-unauthorized "Direct link to reject-unauthorized") ``` Payload: — ``` Токен недействителен или истёк. Нужно показать ошибку аутентификации и попросить пользователя войти заново. *** ### active-speaker-changed[​](#active-speaker-changed "Direct link to active-speaker-changed") ``` Payload: { peer?: Peer } ``` Изменился активный говорящий. Если `peer` — `undefined`, значит активного говорящего нет (тишина). Используется для визуального выделения говорящего участника в интерфейсе. ``` client.observer.on('active-speaker-changed', ({ peer }) => { if (peer) { highlightActiveSpeaker(peer.id); } else { clearActiveSpeaker(); } }); ``` *** ### devices-list-updated[​](#devices-list-updated "Direct link to devices-list-updated") ``` Payload: { video: MediaDeviceInfo[], audio: MediaDeviceInfo[] } ``` Список доступных медиаустройств изменился (устройство подключено или отключено). Рекомендуется обновить UI выбора устройств. Тип payload — [`AvailableMediaDevices`](/docs/sdk-api/interfaces/AvailableMediaDevices.md). Дебаунс Событие может срабатывать несколько раз подряд при одном физическом изменении. Оборачивайте обработчик в `debounce` (300–500 мс): ``` client.observer.on('devices-list-updated', debounce(async (devices) => { await rebuildDeviceSelectors(devices); }, 500)); ``` *** ### track-force-closed[​](#track-force-closed "Direct link to track-force-closed") ``` Payload: { label: TrackLabel } ``` Сервер (модератор/администратор) принудительно закрыл локальный трек участника. Необходимо отразить изменение в UI и очистить трек на стороне клиента. Тип payload — [`TrackForceClosedPayload`](/docs/sdk-api/interfaces/TrackForceClosedPayload.md). Поведение зависит от типа трека: | `label` | Что произошло | Реакция | | -------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `camera` | Администратор выключил камеру | Отключить видео, очистить `cameraTrack`, уведомить пользователя «Администратор выключил вашу камеру» | | `microphone` | Администратор выключил микрофон | Отключить аудио, очистить `microphoneTrack`, уведомить «Администратор выключил ваш микрофон» | | `screen-video` | Администратор остановил демонстрацию экрана | Уведомить «Администратор остановил демонстрацию экрана» | ``` client.observer.on('track-force-closed', ({ label }) => { switch (label) { case 'camera': disableLocalVideo(); showToast('Администратор выключил вашу камеру'); break; case 'microphone': disableLocalAudio(); showToast('Администратор выключил ваш микрофон'); break; case 'screen-video': stopScreenSharing(); showToast('Администратор остановил демонстрацию экрана'); break; } }); ``` *** ### track-failed[​](#track-failed "Direct link to track-failed") ``` Payload: { label: TrackLabel } ``` Локальный трек завершился с ошибкой (например, камера физически отключена или браузер отозвал доступ). Рекомендуется залогировать и при необходимости попробовать пересоздать трек. *** ### denoiser-initializing[​](#denoiser-initializing "Direct link to denoiser-initializing") ``` Payload: boolean ``` SDK начал инициализацию движка шумоподавления. `true` — идёт инициализация, `false` — завершена. *** ### denoiser-enabled[​](#denoiser-enabled "Direct link to denoiser-enabled") ``` Payload: { type: 'rnnoise' | 'asdk' } ``` Шумоподавление включено. Позволяет узнать, какой движок активирован. *** ### denoiser-disabled[​](#denoiser-disabled "Direct link to denoiser-disabled") ``` Payload: { type: 'rnnoise' | 'asdk' } ``` Шумоподавление выключено. *** ### denoiser-fallback[​](#denoiser-fallback "Direct link to denoiser-fallback") ``` Payload: — ``` ASDK встретил повторяющиеся ошибки (3 за 10 сек) и автоматически откатился на `rnnoise`. Дополнительных действий не требуется. *** ### denoiser-failed[​](#denoiser-failed "Direct link to denoiser-failed") ``` Payload: { cause: string, error?: unknown } ``` Критическая ошибка движка шумоподавления. В отличие от `denoiser-fallback`, автоматического отката не происходит. Можно показать предупреждение и предложить пользователю перезагрузить страницу. *** ### effects-enabled[​](#effects-enabled "Direct link to effects-enabled") ``` Payload: — ``` ESDK-эффекты включены на треке (после вызова `videoTrack.enableEffects()`). *** ### effects-disabled[​](#effects-disabled "Direct link to effects-disabled") ``` Payload: — ``` ESDK-эффекты выключены на треке (после вызова `videoTrack.disableEffects()`). *** ### effects-failed[​](#effects-failed "Direct link to effects-failed") ``` Payload: { cause: string, operation: 'enable' | 'disable' | 'apply' | 'runtime', error?: unknown } ``` Ошибка видеоэффектов. Поле `operation` указывает, на каком этапе произошла ошибка. Можно показать предупреждение и предложить отключить эффекты. *** ## События активности участника[​](#события-активности-участника "Direct link to События активности участника") Три события формируют полный жизненный цикл проверки активности. Сервер периодически проверяет, есть ли в канале живые участники. Если никто из них не подтвердит активность до истечения таймаута — **весь канал закрывается** и все участники отключаются: ``` channel-activity-confirmation-required (сервер спрашивает у всех) ↓ ┌─────┴──────────┐ ↓ ↓ acquired expired (хотя бы один (никто не ответил — ответил) канал закрыт) ``` ### channel-activity-confirmation-required[​](#channel-activity-confirmation-required "Direct link to channel-activity-confirmation-required") ``` Payload: { channelId: string, time: number } ``` Сервер запрашивает подтверждение активности у всех участников канала одновременно. Тип payload — [`ActivityConfirmationRequiredPayload`](/docs/sdk-api/interfaces/ActivityConfirmationRequiredPayload.md). `time` — таймаут в миллисекундах до закрытия канала. Если хотя бы один участник вызовет `client.confirmActivity()` до истечения таймаута — канал продолжит работу и все получат `channel-activity-confirmation-acquired`. Если никто не ответит — все получат `channel-activity-confirmation-expired` и канал будет закрыт. Рекомендуется показать пользователю диалог с таймером и кнопкой «Я здесь». ``` client.observer.on('channel-activity-confirmation-required', ({ time }) => { const timeoutSeconds = Math.floor(time / 1000) - 1; // -1 для запаса showActivityConfirmationDialog(timeoutSeconds, { onConfirm: () => client.confirmActivity(), onDismiss: () => client.leave(), }); }); ``` ### channel-activity-confirmation-acquired[​](#channel-activity-confirmation-acquired "Direct link to channel-activity-confirmation-acquired") ``` Payload: ChannelEvent ``` Активность подтверждена — кто-то из участников ответил вовремя. Канал продолжает работу. Скрыть диалог подтверждения у всех участников. ``` client.observer.on('channel-activity-confirmation-acquired', () => { hideActivityConfirmationDialog(); }); ``` ### channel-activity-confirmation-expired[​](#channel-activity-confirmation-expired "Direct link to channel-activity-confirmation-expired") ``` Payload: ChannelEvent ``` Таймаут истёк и никто из участников не подтвердил активность. **Весь канал закрывается** — все участники отключаются одновременно. Необходимо завершить сессию и вывести пользователя из комнаты. ``` client.observer.on('channel-activity-confirmation-expired', () => { navigateToHome(); }); ``` *** ## События Peer[​](#события-peer "Direct link to События Peer") Подписываются через `peer.observer.on()` внутри обработчика `peer-joined`. ### media-published[​](#media-published "Direct link to media-published") ``` Payload: { producerId: string, kind: 'audio' | 'video', label: TrackLabel } ``` Участник опубликовал продюсер. `label` — значение [`TrackLabel`](/docs/sdk-api/enumerations/TrackLabel.md). Это сигнал о том, что продюсер появился — вы **сами решаете**, подписываться ли на него сейчас. Можно подписаться сразу, а можно отложить (например, при пагинации видео — подписываться только на участников, видимых в viewport). Подписка создаётся через `peer.subscribe({ producerId })`; когда данные начинают поступать, эмитируется `track-start`. ``` peer.observer.on('media-published', async ({ producerId, kind, label }) => { await peer.subscribe({ producerId }); // label позволяет различить camera / screen-video / microphone }); ``` ### media-unpublished[​](#media-unpublished "Direct link to media-unpublished") ``` Payload: { producerId: string } ``` Продюсер снят с публикации. Если вы были подписаны — вслед придёт `track-end` (именно там следует убирать элементы из DOM, поскольку в `track-end` всегда есть `label`). `media-unpublished` полезен для обновления внутреннего состояния подписок: убрать `producerId` из списка отслеживаемых. ### publisher-paused[​](#publisher-paused "Direct link to publisher-paused") ``` Payload: { producerId: string } ``` Издатель поставил поток на паузу (например, выключил камеру). Можно показать заглушку вместо видео. ### publisher-resumed[​](#publisher-resumed "Direct link to publisher-resumed") ``` Payload: { producerId: string } ``` Издатель возобновил поток. ### track-start[​](#track-start "Direct link to track-start") ``` Payload: PeerTrack ``` Данные удалённого трека начали поступать — трек готов к воспроизведению. Приходит **только после `peer.subscribe()`**. Это точка для рендеринга элемента в DOM. Payload содержит полный объект [`PeerTrack`](/docs/sdk-api/interfaces/PeerTrack.md) с полями `mediaStreamTrack`, `label`, `kind` и другими. ### track-end[​](#track-end "Direct link to track-end") ``` Payload: PeerTrack ``` Удалённый трек завершился — как правило, вслед за `media-unpublished`. Используйте это событие для удаления элементов из DOM: в payload всегда есть `label`. Payload содержит объект [`PeerTrack`](/docs/sdk-api/interfaces/PeerTrack.md). ### track-paused / track-resumed[​](#track-paused--track-resumed "Direct link to track-paused / track-resumed") ``` Payload: PeerTrack ``` Удалённый трек поставлен на паузу / возобновлён на стороне получателя. ### app-data-updated[​](#app-data-updated "Direct link to app-data-updated") ``` Payload: Record ``` `appData` участника обновился. Используется для передачи прикладных данных в реальном времени: имя, аватар, статус поднятой руки, признак модератора и т.д. ``` peer.observer.on('app-data-updated', (data) => { updatePeerNameInUI(peer.id, data.displayName as string); if (data.isHandRaised) showHandRaisedIndicator(peer.id); }); ``` ### groups-changed[​](#groups-changed "Direct link to groups-changed") ``` Payload: { groups: PeerGroup[] } ``` Изменились группы участника (например, ему выдали или сняли роль модератора). [`PeerGroup`](/docs/sdk-api/type-aliases/PeerGroup.md) — `'moderator' | 'user'`. ``` peer.observer.on('groups-changed', ({ groups }) => { const isModerator = groups.includes('moderator'); updateModeratorBadge(peer.id, isModerator); }); ``` *** ### produce-permissions-changed[​](#produce-permissions-changed "Direct link to produce-permissions-changed") ``` Payload: { labels: TrackLabel[] } ``` Изменились права участника на публикацию треков (модератор выдал или отозвал разрешение). Тип payload — [`ProducePermissionsChangedPayload`](/docs/sdk-api/interfaces/ProducePermissionsChangedPayload.md). ``` peer.observer.on('produce-permissions-changed', ({ labels }) => { updatePermissionsUI(labels); }); ``` *** ### track-failed (Peer)[​](#track-failed-peer "Direct link to track-failed (Peer)") ``` Payload: PeerTrack ``` Удалённый трек участника завершился с ошибкой. Отличается от клиентского `track-failed` (который относится к локальным трекам): здесь payload — объект удалённого трека [`PeerTrack`](/docs/sdk-api/interfaces/PeerTrack.md). *** ### connection-quality-changed[​](#connection-quality-changed "Direct link to connection-quality-changed") ``` Payload: { connectionQuality: number } ``` Обновилась оценка качества соединения участника. Используется для отображения индикатора качества рядом с видео участника. ``` peer.observer.on('connection-quality-changed', ({ connectionQuality }) => { updateQualityIndicator(peer.id, connectionQuality); }); ``` *** ## Типичный паттерн подписки[​](#типичный-паттерн-подписки "Direct link to Типичный паттерн подписки") Канонический порядок подписок и синхронизации описан в разделе [Участники → Синхронизация состояния после join()](/docs/web-sdk/peers.md#%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5-join). В справочнике событий выше перечислены payload и назначение каждого события. --- # Установка ## npm-пакет[​](#npm-пакет "Direct link to npm-пакет") ``` npm install @livedigital/client ``` или через yarn: ``` yarn add @livedigital/client ``` ## TypeScript[​](#typescript "Direct link to TypeScript") Пакет включает декларации типов. Дополнительных `@types/*` не требуется. ``` import { Client } from '@livedigital/client'; import type { ClientParams, JoinChannelParams } from '@livedigital/client'; ``` Полные типы — [`ClientParams`](/docs/sdk-api/interfaces/ClientParams.md), [`JoinChannelParams`](/docs/sdk-api/interfaces/JoinChannelParams.md) — в справочнике API. ## Статические файлы[​](#статические-файлы "Direct link to Статические файлы") SDK загружает WASM-модули, ML-модели и воркеры в runtime. Их можно раздавать из своего приложения или использовать CDN `https://static.prod.livedigital.space`; базовый путь настраивается через параметр `staticFilesPath` в конструкторе `Client`. Полный список файлов, URL и примеры конфигурации — в разделе [Видеоэффекты и статические файлы](/docs/web-sdk/video-effects.md#%D1%84%D0%B0%D0%B9%D0%BB%D1%8B-sdk-%D0%BD%D0%B0-cdn). --- # Введение **livedigital Web SDK** — клиент сервиса livedigital для браузеров. Поставляется как npm-пакет [`@livedigital/client`](https://www.npmjs.com/package/@livedigital/client) и предоставляет всё необходимое для встраивания видеоконференций в веб-приложение. SDK реализует: * сигналинг (подключение, обмен командами с медиа-сервером) * логику восстановления соединения при реконнекте * анализ качества соединения (QoE) и WebRTC-метрики * работу с локальными и удалёнными медиа-треками (камера, микрофон, экран, custom) * видеоэффекты (размытие и замена фона, beautification, smart zoom) * шумоподавление на базе ML * управление участниками, ролями * выделение активного спикера * транспорт метаданных приложения (`appData`) ## Совместимость[​](#совместимость "Direct link to Совместимость") | Среда | Минимальная версия | | ------------ | ------------------ | | Chrome-based | 90+ | | Safari | 15+ | Signaling token Для подключения необходим действующий `signalingToken`, выпущенный через Moodhood API для конкретного участника. Подробнее — в разделе [Подготовка комнаты](/docs/web-sdk/room-setup.md). При **on-premise** развёртывании livedigital signaling-токен можно [генерировать своим бэкендом](/docs/tutorials/user_access/generate_signaling_token.md) — без обращения к публичной Moodhood API. ## Какой режим работы выбрать[​](#какой-режим-работы-выбрать "Direct link to Какой режим работы выбрать") | Сценарий | Рекомендация | | ---------------------------------------------------- | ---------------------------------------------- | | Конференция до \~1000 участников, все могут говорить | SDK, роль `host` для всех | | Вебинар: несколько спикеров + тысячи слушателей | SDK, спикеры — `host`, слушатели — `audience` | | Звонок 1-на-1 | SDK с флагом `isP2pCall: true` | | Встраивание без кода | [iFrame интеграция](/docs/tutorials/iframe.md) | ## Структура документации[​](#структура-документации "Direct link to Структура документации") **Начало работы** * [Установка](/docs/web-sdk/installation.md) — npm install * [Быстрый старт](/docs/web-sdk/quickstart.md) — минимальный рабочий пример **Концепции** * [Жизненный цикл SDK](/docs/web-sdk/sdk-lifecycle.md) — состояния, переходы, что когда делать * [Подготовка комнаты](/docs/web-sdk/room-setup.md) — что сделать на бэкенде до входа **Основной API** * [Инициализация Client](/docs/web-sdk/client-initialization.md) — конструктор и параметры * [Подключение к комнате](/docs/web-sdk/connection.md) — `join()`, reconnection, `leave()` * [Участники](/docs/web-sdk/peers.md) — работа с Peer-объектами, подписки, права * [Медиа-треки](/docs/web-sdk/media-tracks.md) — создание, публикация и жизненный цикл треков * [Отображение видео и аудио](/docs/web-sdk/rendering-media.md) — рендеринг треков, аудиовыход * [Экран настройки перед входом](/docs/web-sdk/pre-call-setup.md) — лобби, превью камеры, выбор устройств **Медиа** * [Управление устройствами](/docs/web-sdk/devices.md) — обнаружение и переключение камер/микрофонов * [Демонстрация экрана](/docs/web-sdk/screen-sharing.md) — захват и публикация экрана * [Шумоподавление](/docs/web-sdk/noise-suppression.md) — rnnoise и ASDK: включение и настройка * [Видеоэффекты и статические файлы](/docs/web-sdk/video-effects.md) — blur, замена фона, ESDK/ASDK, CDN-файлы **Продвинутые темы** * [Режим вебинара](/docs/web-sdk/webinar-mode.md) — host/audience, лимиты, модель взаимодействия * [Метаданные участника (appData)](/docs/web-sdk/appdata-guide.md) — передача данных между участниками * [Управление полосой пропускания](/docs/web-sdk/bandwidth-management.md) — simulcast, subscribe/unsubscribe * [P2P режим](/docs/web-sdk/p2p-mode.md) — оптимизация для звонков 1-на-1 * [Качество соединения](/docs/web-sdk/network-quality.md) — QoE-метрики, ICE policy **Справочники** * [Обработка ошибок](/docs/web-sdk/error-handling.md) — что бросает SDK и как реагировать * [Справочник событий](/docs/web-sdk/events-reference.md) — все события Client и Peer * [SDK API Reference](/docs/sdk-api/classes/Client.md) — полный автогенерируемый справочник типов и методов * [React-обёртка](/docs/web-sdk/react-integration.md) — пример реализации React-приложения для основных сценариев * [Устранение неполадок](/docs/web-sdk/troubleshooting.md) — типичные ошибки и решения --- # Медиа-треки Трек — единица медиапотока: видео с камеры, аудио с микрофона, захват экрана или кастомный поток. SDK предоставляет методы для создания, публикации, паузы, замены и удаления треков. ## Камера[​](#камера "Direct link to Камера") ``` const videoTrack = await client.createCameraVideoTrack({ video: { deviceId: { exact: 'device-id' }, width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: { ideal: 30 }, }, encoderConfig: { preferredCodec: 'vp8', }, effects: true, // включить слот для ESDK-эффектов stopTrackOnPause: true, // при паузе останавливать MediaStreamTrack (гасит индикатор камеры на устройстве) }); await videoTrack.publish(); ``` Полные параметры — [`CreateCameraVideoTrackOptions`](/docs/sdk-api/type-aliases/CreateCameraVideoTrackOptions.md). ## Микрофон[​](#микрофон "Direct link to Микрофон") ``` const audioTrack = await client.createMicrophoneAudioTrack({ audio: { deviceId: { exact: 'mic-device-id' } }, noiseSuppression: true, encoderConfig: { preferredCodec: 'opus', enableDtx: true, // Discontinuous Transmission enableFec: true, // Forward Error Correction }, }); await audioTrack.publish(); ``` Полные параметры — [`CreateMicrophoneAudioTrackOptions`](/docs/sdk-api/type-aliases/CreateMicrophoneAudioTrackOptions.md). ## Жизненный цикл трека[​](#жизненный-цикл-трека "Direct link to Жизненный цикл трека") ``` // Пауза и возобновление await videoTrack.pause(); await videoTrack.resume(); // Замена источника без обрыва WebRTC-соединения await videoTrack.replaceTrack(newMediaStreamTrack); // Снять с публикации и удалить await client.deleteTrack(videoTrack); ``` ## Локальное видео[​](#локальное-видео "Direct link to Локальное видео") ### Превью в лобби (до join)[​](#превью-в-лобби-до-join "Direct link to Превью в лобби (до join)") Для экрана предварительной настройки передайте `isPreview: true` — трек не будет опубликован в канал и не создаст producer: ``` const previewTrack = await client.createCameraVideoTrack({ video: { deviceId: { exact: selectedCameraId } }, isPreview: true, stopTrackOnPause: true, }); const previewVideo = document.getElementById('preview-video') as HTMLVideoElement; previewVideo.muted = true; previewVideo.autoplay = true; previewVideo.playsInline = true; previewVideo.srcObject = new MediaStream([previewTrack.mediaStreamTrack]); ``` Почему isPreview позволяет держать два трека с одной камеры SDK хранит треки в Map с ключом по `TrackLabel`. Обычный трек с камеры получает метку `camera`; при `isPreview: true` метка становится `CameraPreview` — поэтому оба трека сосуществуют без конфликта. Опубликовать `isPreview`-трек **нельзя**: `publish()` на нём выбросит ошибку. Это намеренно — preview существует только для локального отображения. Подробнее о паттерне лобби — в разделе [Экран настройки перед входом](/docs/web-sdk/pre-call-setup.md). ### Своё видео в гриде (после publish)[​](#своё-видео-в-гриде-после-publish "Direct link to Своё видео в гриде (после publish)") После вызова `track.publish()` событие `track-start` придёт на собственном пире (`peer.isMe === true`) — точно так же, как для удалённых участников. Обрабатывайте его в том же рендерере: ``` client.observer.on('channel-state-synced', () => { const mePeer = client.peers?.find(p => p.isMe); if (mePeer) setupPeerRendering(mePeer, myContainer); // тот же setupPeer, что и для других }); const videoTrack = await client.createCameraVideoTrack({ effects: true }); await videoTrack.publish(); // → вызовет track-start на mePeer ``` muted для своего видео Элемент `