Архитектурные инварианты
Инварианты ниже выведены из текущей структуры кода: Server/Processor, Engine/Proto, Engine/Controller, WSMServer, FilePlayer и Recorder. Это правила модели VideoGrace, а не временные workaround'ы конкретного клиента.
1. Control plane является источником правды
Все решения о пользователях, конференциях, правах и устройствах проходят через CommandLoop и Processor.
flowchart LR
Control[CommandLoop JSON]
Processor[Processor]
Clients[ClientContainer]
Conferences[ConferenceContainer]
Devices[DeviceContainer]
Media[WSMedia / UDP]
Control --> Processor
Processor --> Clients
Processor --> Conferences
Processor --> Devices
Media -.->|только RTP/RTCP payload| Processor
Правила:
- Media transport не создает и не удаляет устройства.
- Blob transport не меняет состояние конференции.
- UI или bot не должны считать локальное состояние устройства подтвержденным до server event.
device_connect,device_disconnect,change_member_state,connect_to_conference_responseважнее локальных предположений клиента.
2. Идентичности не взаимозаменяемы
В системе есть несколько разных идентификаторов. Их нельзя подменять друг другом.
| Идентификатор | Где живет | Для чего используется |
|---|---|---|
client_id |
ClientContainer, Member |
Личность пользователя/сервиса. |
connection_id |
ClientContainer |
Конкретное command-соединение клиента. |
session_t |
HttpServer, ClientContainer, ConferenceContainer |
Runtime WebSocket session. |
access_token |
ClientContainer |
Привязка slave channels (WSMedia, BlobChannel) к command login. |
conference.tag |
ConferenceContainer, protocol |
Stable ключ конференции для клиентов и URL. |
device_id |
DeviceContainer, ClientContainer |
Control lifecycle устройства. |
author_ssrc |
TranslatorPool, media sessions |
SSRC потока автора/publisher. |
receiver_ssrc |
TranslatorPool, media sessions |
SSRC receiving side для конкретного receiver. |
Ключевое правило: device_id не является media routing key. Media routing key — ssrc.
3. Каналы разделены по назначению
flowchart TB
Client[Client]
Command[CommandLoop<br/>JSON commands]
Blob[BlobChannel<br/>binary blob frames]
Media[WSMedia<br/>binary media frames]
Server[Server]
Client <-->|state and commands| Command
Client <-->|files, voice, speed-test| Blob
Client <-->|RTP/RTCP| Media
Command --> Server
Blob --> Server
Media --> Server
Правила:
CommandLoopпереносит JSON-команды.BlobChannelпереносит WSM blob frames.WSMediaпереносит только WSM media frames с RTP/RTCP payload.BlobChannelиWSMediaлогинятся черезaccess_token, а не через пароль.- Закрытие одного канала не должно неявно трактоваться как закрытие всех остальных, кроме явного logout/conference cleanup.
4. Сервер считает grants и видимость
Клиент сообщает возможности при входе в конференцию (has_camera, has_microphone, has_demonstration), но окончательное решение принимает сервер.
ConferenceContainer хранит:
- membership;
- grants;
- order;
- флаги наличия камеры, микрофона, демонстрации;
- правила видимости/слышимости через
IsCameraVisible,IsMicrophoneHear,IsDemonstrationVisible.
sequenceDiagram
participant Client
participant Processor
participant Conf as ConferenceContainer
participant Other
Client->>Processor: connect_to_conference_request(has_camera, has_microphone)
Processor->>Conf: AddClient + calculate grants/visibility
Processor-->>Client: connect_to_conference_response
Processor-->>Other: change_member_state(filtered by visibility)
Правила:
- Клиент не должен сам решать, кому видна камера или слышен микрофон.
- Moderator/presenter/read-only behavior должен идти через grants.
- Любая новая команда управления участниками должна проверять grants на сервере.
5. Device lifecycle двухфазный
Локальная публикация начинается с capture, но устройство становится частью системы только после server round-trip.
stateDiagram-v2
[*] --> NoDevice
NoDevice --> LocalCaptureStarted: local capture ok
LocalCaptureStarted --> ParamsSent: device_params
ParamsSent --> ServerDeviceCreated: device_connect my=1
ServerDeviceCreated --> MediaAttached: attach author_ssrc
MediaAttached --> Publishing: RTP/RTCP flow
Publishing --> DisconnectSent: device_disconnect
DisconnectSent --> NoDevice: local media stopped
Правила:
device_paramsпросит сервер создать устройство.device_connect my=1подтверждаетdevice_id,author_ssrc,port, codec иsecure_key.device_disconnectудаляет control device.- Media attach без server-provided
ssrcиportнедопустим. - Быстрые UI toggle операции должны сериализоваться на уровне lifecycle, чтобы не создать несколько незавершенных devices.
6. Receive lifecycle симметричен control events
Remote receive начинается с device_connect my=0 и заканчивается device_disconnect.
flowchart TB
DC[device_connect my=0]
Type{device_type}
Audio[Audio renderer/session]
Video[Video renderer/session]
Attach[Attach receiver_ssrc]
Init[Send RTP init / RTCP if needed]
Frames[Receive RTP frames]
DD[device_disconnect]
Stop[Stop renderer and unregister ssrc]
DC --> Type
Type -->|Microphone| Audio
Type -->|Camera/Demonstration| Video
Audio --> Attach
Video --> Attach
Attach --> Init --> Frames
DD --> Stop
Правила:
- Один remote device соответствует одному renderer/session в клиенте.
receiver_ssrcрегистрируется в media demux.- Video receiver должен уметь запросить keyframe через RTCP.
device_disconnectобязан удалить renderer/session и SSRC registration.
7. Media routing построен вокруг SSRC и TranslatorPool
TranslatorPool связывает device_id, authorSSRC, receiverSSRC и translator port. WSMServer связывает ssrc с UDP socket и WebSocket session.
flowchart LR
Device[device_id]
Author[authorSSRC]
Receiver[receiverSSRC]
Port[translator port]
WSM[WSMServer]
UDP[UDP socket]
Device --> Author
Device --> Receiver
Author --> Port
Receiver --> Port
Receiver --> WSM
Author --> WSM
WSM <--> UDP
Правила:
TranslatorPool::AddTranslation(deviceId, authorSSRC)создает translation для publisher.TranslatorPool::AddReceiver(deviceId, receiverSSRC)добавляет receiving side.WSMServerне маршрутизирует поdevice_id; он маршрутизирует поssrc.- Одна
WSMediasession может владеть несколькимиssrc. - Закрытие media session чистит все
ssrc, принадлежащие этой session.
8. Записи в одну WebSocket session сериализуются
IWSSession::write() может вызываться из разных подсистем: Processor, BlobManager, WSMServer, HTTP/API handlers. Для одного WebSocket stream все async_write должны идти через executor/strand этой session.
flowchart TB
A[Processor write]
B[WSMServer write]
C[BlobManager write]
Queue[websocket_session write queue]
Strand[session executor / strand]
WS[websocket::stream]
A --> Queue
B --> Queue
C --> Queue
Queue --> Strand --> WS
Правила:
- Нельзя запускать параллельные
async_writeв одинwebsocket::stream. - Queue защищает порядок сообщений, но операции stream должны выполняться на executor/strand.
- Это особенно важно для mux-каналов, где несколько SSRC или blob operations сходятся в одну session.
9. Reconnect восстанавливает transport, а не дублирует domain state
Reconnect не должен создавать новые устройства, если control state не изменился.
| Что упало | Что восстанавливать |
|---|---|
CommandLoop |
Авторизацию, conference membership, актуальный state. |
WSMedia |
Transport session, SSRC registrations, RTP init/RTCP bootstrap. |
BlobChannel |
Только незавершенные blob operations. |
| UDP media path | Socket/receiver binding без повторного создания control devices. |
Правила:
- Transport reconnect не равен
device_params. - Повторный
device_paramsдопустим только если действительно создается новое устройство. - Receiver reconnect должен восстанавливать media bootstrap для существующих
receiver_ssrc.
10. Service clients — такие же участники протокола
FilePlayer, Recorder, Consolidator используют тот же Engine/Controller и те же events, что и пользовательские клиенты.
flowchart LR
Controller[Engine/Controller]
UI[Native/Web UI]
FilePlayer[FilePlayer publisher]
Recorder[Recorder receiver]
Server[Server]
UI --> Controller
FilePlayer --> Controller
Recorder --> Controller
Controller <--> Server
Правила:
- Bot/service не должен обходить
CommandLoop, если он участвует в conference lifecycle. - Publisher service следует модели
FilePlayer:CreateCapturer -> ConnectCapturer -> Start RTP. - Receiver service следует модели
Recorder:DeviceConnect -> RendererSession -> Decode/Process. - Новый AI client должен быть описан как service client с явным control/media lifecycle.
Антиинварианты
Такой код почти наверняка ошибочен:
- Media routing по
device_id. - Создание локального device без ожидания server
device_connect my=1. - Удаление shared media transport на каждый
device_disconnect. - Повторный
device_paramsкак реакция на media reconnect. - Решение grants/visibility на клиенте без серверного подтверждения.
- Запись в один WebSocket stream из нескольких потоков без session executor/strand.
- Новый протокол в формате, отличном от
{"command_name":{...}}, без миграционного плана.