Skip to content

WebSocket/WSS API

VideoGrace использует WebSocket для трех разных каналов. Формально это один endpoint сервера, но смысл соединения задается connect_request.channel_type.

ChannelType Значение Формат frames Назначение
CommandLoop 0 text JSON Логин, конференции, устройства, звонки, сообщения.
WSMedia 1 text JSON на логине, затем binary WSM media frames RTP/RTCP media.
BlobChannel 2 text JSON на логине, затем binary WSM blob frames Legacy blob frames. Новые файлы идут через HTTPS storage API.

Для browser media основной путь - WebRTC. Его signaling идет по CommandLoop командами webrtc_routes_request, webrtc_offer, webrtc_answer и webrtc_ice_candidate. WSMedia остается initial/forced fallback, а не основной playback path.

ClientType:

ClientType Значение Назначение
Ordinal 0 Обычный пользовательский клиент или простой сервис, публикующий media.
MediaProcessor 1 Media-processing service.
Connector 2 Интеграционный коннектор.
Recorder 3 Сервис записи.

CommandLoop login

Клиент открывает ws://host:port или wss://host:port и первым сообщением отправляет JSON:

{
  "connect_request": {
    "client_type": 0,
    "channel_type": 0,
    "client_version": 100,
    "system": "Web",
    "login": "user@example.com",
    "password": "secret",
    "control_instance_id": "ci_mq2a..."
  }
}

Повторный вход может использовать access_token вместо login/password, если token еще валиден:

{
  "connect_request": {
    "client_type": 0,
    "channel_type": 0,
    "client_version": 100,
    "system": "Web",
    "access_token": "jwt-or-opaque-token",
    "control_instance_id": "ci_mq2a..."
  }
}

Ответ:

{
  "connect_response": {
    "result": 1,
    "server_version": 100,
    "id": 223900001,
    "connection_id": 1,
    "access_token": "opaque-token",
    "refresh_token": "refresh-token",
    "name": "User",
    "secure_key": "base64-or-empty",
    "server_name": "VideoGrace",
    "options": 0,
    "grants": 0,
    "max_output_bitrate": 4096,
    "restore_stale_session": true,
    "stale_conference_tag": "radio"
  }
}

result=1 означает OK. Остальные значения описаны в Engine/Proto/CmdConnectResponse.h.

Ключевые поля:

Поле Назначение
id client_id пользователя.
connection_id Текущий server-side command connection.
access_token Bearer token для дополнительных каналов и REST API. При включенном VG_JWT_SECRET это JWT HS256; иначе legacy opaque token.
refresh_token Долгий token только для REST refresh, не передается в media/blob/CAN.
grants Битовая маска прав.
max_output_bitrate Ограничение исходящего bitrate для клиента.
restore_stale_session Сервер восстановил stale command session вместо чистого нового входа.
stale_conference_tag Конференция, состояние которой было восстановлено.

control_instance_id - стабильный идентификатор экземпляра web-client. Он хранится на клиенте, отправляется в каждом CommandLoop connect_request и нужен серверу для stale restore. Это не секрет и не замена access_token.

sequenceDiagram
    participant Client
    participant WS as CommandLoop WS
    participant Server

    Client->>WS: open ws/wss
    Client->>Server: connect_request(channel_type=0, login/password or access_token, control_instance_id)
    Server-->>Client: connect_response(result=OK, access_token, restore_stale_session?)
    Server-->>Client: conferences_list / contact_list / group_list

Дополнительные каналы по access_token

WSMedia и BlobChannel не передают логин/пароль. Они используют token из CommandLoop.

{
  "connect_request": {
    "channel_type": 1,
    "access_token": "opaque-token"
  }
}

Для BlobChannel используется channel_type: 2.

Refresh token здесь не используется. Если access token истек, клиент сначала обновляет его через REST refresh и только потом открывает дополнительные WebSocket-каналы.

Основной control lifecycle конференции

sequenceDiagram
    participant Client
    participant Server
    participant Others as Other members

    Client->>Server: connect_to_conference_request(tag, connect_members, has_camera, has_microphone)
    Server-->>Client: change_member_state(existing members)
    Server-->>Client: connect_to_conference_response(result=OK)
    Server-->>Client: device_connect(existing remote devices)
    Server-->>Others: change_member_state(new member)

Пример:

{
  "connect_to_conference_request": {
    "tag": "default",
    "connect_members": 1,
    "has_camera": 1,
    "has_microphone": 1
  }
}

Создание локального устройства

Публикация mic/cam/screen начинается в control plane:

{
  "device_params": {
    "id": 0,
    "ssrc": 0,
    "device_type": 1,
    "ord": 0,
    "name": "Camera",
    "metadata": "",
    "resolution": 31457920,
    "color_space": 0
  }
}

Сервер отвечает device_connect для автора и рассылает remote device_connect другим участникам:

{
  "device_connect": {
    "connect_type": 1,
    "device_type": 1,
    "device_id": 1001,
    "client_id": 223900001,
    "metadata": "",
    "receiver_ssrc": 0,
    "author_ssrc": 1208,
    "address": "",
    "port": 5061,
    "name": "Camera",
    "resolution": 31457920,
    "color_space": 0,
    "video_codec": 0,
    "audio_codec": 0,
    "my": 1,
    "secure_key": "..."
  }
}

DeviceType:

DeviceType Значение
Camera 1
Demonstration 2
Avatar 3
Microphone 4
VideoRenderer 5
AudioRenderer 6

ConnectType:

ConnectType Значение
CreatedDevice 1
ConnectRenderer 2

WebRTC signaling over CommandLoop

WebRTC не открывает отдельный signaling WebSocket. Все SDP/ICE сообщения идут как JSON-команды по CommandLoop, а media payload идет по ICE/DTLS/SRTP между browser и RTC node / WebRTC Gateway.

Перед созданием endpoint'а клиент запрашивает доступные RTC routes:

{ "webrtc_routes_request": {} }

Ответ:

{
  "webrtc_routes": {
    "routes": [
      {
        "rtc_node_id": "rtc-eu-1",
        "role": "rtc-translator",
        "version": "3.0.0",
        "capabilities": ["rtc", "webrtc", "rtp_bridge"]
      }
    ]
  }
}

rtc_node_id передается в webrtc_offer и webrtc_ice_candidate. Если route не подключился при первичном создании endpoint'а в режиме auto, клиент может попробовать следующую route и затем WSMedia fallback. Если WebRTC endpoint уже был connected/ok, дальнейший stale/stall recovery выполняется через WebRTC restart/backoff, без WSM fallback.

webrtc_offer

{
  "webrtc_offer": {
    "peer_id": "remote-video-1006-228800002",
    "conference_tag": "radio",
    "scope": "video-subscribe",
    "rtc_node_id": "rtc-eu-1",
    "endpoint_id": "video-subscribe-uuid",
    "sdp": "v=0...",
    "device_id": 1006,
    "author_ssrc": 1042,
    "receiver_ssrc": 1044,
    "port": 5060
  }
}

Поля binding берутся из обычного control lifecycle устройств:

Поле Назначение
peer_id Диагностический id endpoint'а на клиенте.
conference_tag Конференция endpoint'а.
scope audio, video, audio-subscribe, video-subscribe.
rtc_node_id Выбранная route из webrtc_routes; пустое значение разрешает server fallback gateway, если он доступен.
endpoint_id Клиентский id конкретного RTCPeerConnection; коррелирует answer и ICE.
sdp Local SDP offer.
device_id Server-side device id из device_connect.
author_ssrc SSRC источника/publisher.
receiver_ssrc SSRC receiver'а для subscribe path.
port Translator port из device_connect.

Для publish (scope=audio/video) клиент использует self device_connect my=1 после device_params. Для subscribe (scope=audio-subscribe/video-subscribe) клиент использует remote device_connect my=0.

webrtc_answer

{
  "webrtc_answer": {
    "peer_id": "remote-video-1006-228800002",
    "conference_tag": "radio",
    "scope": "video-subscribe",
    "rtc_node_id": "rtc-eu-1",
    "endpoint_id": "video-subscribe-uuid",
    "sdp": "v=0..."
  }
}

webrtc_ice_candidate

{
  "webrtc_ice_candidate": {
    "peer_id": "remote-video-1006-228800002",
    "conference_tag": "radio",
    "scope": "video-subscribe",
    "rtc_node_id": "rtc-eu-1",
    "endpoint_id": "video-subscribe-uuid",
    "candidate": {
      "candidate": "candidate:...",
      "sdpMid": "0",
      "sdpMLineIndex": 0
    }
  }
}

endpoint_id обязателен для корректного reconnect/restart: после stale клиент создает новый RTCPeerConnection и новый offer, но сохраняет тот же device_id/sourceDevice binding. Повторный device_params для уже опубликованного локального mic/cam/screen делать нельзя.

WebRTC media lifecycle

sequenceDiagram
    participant Client
    participant Control as CommandLoop
    participant RTC as RTC node / Gateway
    participant Translator

    Client->>Control: device_params(local publish)
    Control-->>Client: device_connect(my=1, device_id, author_ssrc, port)
    Client->>Control: webrtc_offer(scope=audio/video, binding)
    Control-->>Client: webrtc_answer(endpoint_id)
    Client-->>RTC: ICE/DTLS/SRTP publish
    RTC-->>Translator: RTP/RTCP by author_ssrc

    Control-->>Client: device_connect(my=0, receiver_ssrc, port)
    Client->>Control: webrtc_offer(scope=audio/video-subscribe, binding)
    Control-->>Client: webrtc_answer(endpoint_id)
    Translator-->>RTC: RTP/RTCP by source SSRC
    RTC-->>Client: WebRTC remote track

Инварианты:

  • device_connect создает media endpoint в control lifecycle, но не открывает transport сам по себе.
  • WebRTC publish/subscribe не создает и не удаляет server-side devices.
  • Один WebRTC endpoint соответствует одному local/remote media binding.
  • Local stale recovery переиспользует существующий device_id, author_ssrc и port.
  • Remote stale recovery переиспользует существующий remote device_connect binding.
  • Если endpoint уже был успешно поднят через WebRTC, stall/reconnect не должен переключать его на WSMedia.

Media over WSMedia

WSMedia - fallback transport. Он используется, если WebRTC endpoint не поднялся изначально в режиме auto, либо если клиент принудительно выбран в режим vg.mediaTransport=wsm. После connect_response на WSMedia клиент отправляет binary frames:

packet-beta
    0-7: "msg_type=1"
    8-15: "flags=0"
    16-47: "ssrc"
    48-63: "port"
    64-79: "media_type"
    80-127: "RTP/RTCP payload..."

Правила:

  • ssrc выбирает media route внутри WSMServer;
  • port указывает порт внутреннего media translator;
  • media_type=1 для RTP, media_type=2 для RTCP;
  • RTP payload types в текущем browser path: H.264 96, Opus 111; VP8 остается legacy/fallback path для старых native/media services;
  • один WSMedia может переносить много SSRC.

RequestMediaAddresses

Legacy native клиенты и тестеры могут запросить media адреса:

{ "request_media_addresses": {} }

Ответ содержит rtp://host:port адреса translator pool и ws://host:port fallback endpoint. Для browser и новых mobile/KMP клиентов primary media path - WebRTC: сначала используйте webrtc_routes_request, затем webrtc_offer/webrtc_answer/webrtc_ice_candidate.

HTTPS storage API

Новые файлы, изображения и будущие записи передаются по HTTPS, отдельно от WebSocket control/media каналов.

Start upload

POST /api/storage/uploads
Authorization: Bearer <access_token>
Content-Type: application/json

Тело запроса содержит content_type, filename и size. Ответ возвращает upload_id, blob_id, max_chunk_size, max_object_size и будущий url.

Append chunk

PUT /api/storage/uploads/<upload_id>
Authorization: Bearer <access_token>
Content-Type: application/octet-stream
X-Upload-Offset: <bytes>

Сервер проверяет владельца upload session, ожидаемый offset, максимальный размер чанка и общий лимит объекта.

Complete upload

POST /api/storage/uploads/<upload_id>/complete
Authorization: Bearer <access_token>

После complete объект находится в состоянии готового blob, но остается в scope upload, пока клиент не отправит сообщение со ссылкой на /api/storage/blobs/<blob_id>.

Read blob

GET /api/storage/blobs/<blob_id>
Authorization: Bearer <access_token>
Range: bytes=<from>-<to>

Range опционален. Сервер проверяет ACL, выставляет корректный Content-Type, Content-Length, Content-Range и Content-Disposition с исходным именем файла. Query token для приватных файлов не используется.

Ошибки и reconnect

Ситуация Ожидаемое поведение
Control WS закрыт без stale restore Клиент переподключается и выполняет обычный login flow. Если stale не восстановлен, conference state пересобирается обычным join flow.
Control WS закрыт, затем restore_stale_session=true Клиент не делает полный rejoin и не повторяет device_params; сохраняет conference/device state и запускает WebRTC media recovery.
Remote WebRTC subscribe stalled после active RTC Остановить текущий RTCPeerConnection, создать новый endpoint с тем же binding, отправить новый webrtc_offer, retry с backoff. WSMedia fallback запрещен.
Local WebRTC publish disconnected/failed после active RTC Republish через новый RTCPeerConnection с тем же device_id/author_ssrc/port. Новый device_params запрещен.
Initial WebRTC endpoint не получил answer/ICE connected в auto mode Можно попробовать следующую RTC route, затем WSMedia fallback для этого endpoint'а.
Initial WebRTC endpoint не поднялся в webrtc mode Не переключаться в WSMedia; показать ошибку/красный RTC и оставить диагностику WebRTC.
WSMedia закрыт Только для WSM fallback/forced sessions: media transport reconnect, затем повтор RTP init/ForceKeyFrame по зарегистрированным SSRC.
BlobChannel закрыт Reconnect/retry конкретной blob операции.
device_disconnect Удалить устройство и SSRC handler, но не закрывать общий media transport.