Skip to content

Архитектурные инварианты

Инварианты ниже выведены из текущей структуры кода: 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.
  • Одна WSMedia session может владеть несколькими 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":{...}}, без миграционного плана.