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_connectbinding. - Если 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, Opus111; 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. |