Video flow
Video flow в VideoGrace разделяет control lifecycle камеры/демонстрации и media lifecycle видеопотока. Камера создается как server-side device через control channel, а encoded video frames передаются через выбранный media transport.
Для web-client основной video transport - WebRTC:
- local camera/screen publish идет через
WebRTCPublishSession; - remote camera/screen receive идет через
WebRTCVideoSubscribeSession; - WSM/
MediaChannelостается fallback для initial WebRTC failure или forcedvg.mediaTransport=wsm.
Local camera publish через WebRTC
sequenceDiagram
participant UI
participant Client as VideograceClient
participant Control as Control channel
participant Camera as CameraSession
participant Pub as WebRTCPublishSession
participant RTC as RTC node / WebRTC Gateway
participant Translator
UI->>Client: toggleCam(true)
Client->>Camera: start capture
Camera-->>Client: capture ready(width, height, fps)
Client->>Control: device_params(device_type=cam, resolution, codec)
Control-->>Client: device_connect(my=1, device_id, author_ssrc, port)
Client->>Pub: create RTCPeerConnection + add video track
Pub->>Control: webrtc_offer(scope=video, binding)
Control-->>Pub: webrtc_answer + ICE
Pub->>RTC: ICE/DTLS/SRTP H.264
RTC->>Translator: plain RTP(author_ssrc, port)
Для screen share используется тот же publish path, но device type и peer_id отличаются (local-screen-*). Binding для publish берется из self device_connect: device_id, author_ssrc, port, secure_key. Повторный publish после stale использует тот же sourceDevice и не отправляет новый device_params.
Remote video receive через WebRTC
sequenceDiagram
participant Control as Control channel
participant Client as VideograceClient
participant Sub as WebRTCVideoSubscribeSession
participant RTC as RTC node / WebRTC Gateway
participant Translator
participant Renderer
Control-->>Client: device_connect(my=0, device_type=cam/screen, receiver_ssrc, port)
Client->>Sub: create RTCPeerConnection(recvonly)
Sub->>Renderer: attach video element to slot
Sub->>Control: webrtc_offer(scope=video-subscribe, binding)
Control-->>Sub: webrtc_answer + ICE
RTC->>Translator: register receiver_ssrc / request keyframe
Translator-->>RTC: plain RTP(author_ssrc)
RTC-->>Sub: WebRTC video track
Sub->>Renderer: HTMLVideoElement metadata/playing
Pipeline
flowchart LR
subgraph Publish[Local WebRTC publish]
Capture[Camera capture]
Convert[Format convert / rotate]
Track[MediaStreamTrack video]
PCOut[RTCPeerConnection sendonly/sendrecv]
GatewayIn[RTC node]
RTPOut[Translator RTP author_ssrc]
end
subgraph Receive[Remote WebRTC receive]
RTPIn[Translator RTP author_ssrc]
GatewayOut[RTC node]
PCIn[RTCPeerConnection recvonly]
TrackIn[Remote video track]
Render[HTMLVideoElement]
end
Capture --> Convert --> Track --> PCOut --> GatewayIn --> RTPOut
RTPIn --> GatewayOut --> PCIn --> TrackIn --> Render
Keyframe flow
sequenceDiagram
participant Receiver as WebRTCVideoSubscribeSession
participant RTC as RTC node / WebRTC Gateway
participant Translator
participant Sender as Remote publisher
Receiver->>RTC: WebRTC subscribe connected
RTC->>Translator: register receiver_ssrc
RTC->>Sender: PLI/FIR/keyframe request
Sender-->>Translator: next keyframe RTP
Translator-->>RTC: RTP keyframe frames
RTC-->>Receiver: WebRTC video frames
Receiver->>Receiver: video element reaches metadata/playing
Keyframe нужен при первом подключении remote video, после WebRTC restart и после decode stalls. Запросы keyframe должны быть throttled, чтобы ошибка декодера не превращалась в RTCP spam.
WSM fallback video path
WSM fallback сохраняет старую схему:
- local
CameraSession/ScreenSessionкодирует H.264 через browser encoder/WebCodecs path; - RTP/H.264 packetization идет через JS transport helpers;
MediaMuxSocketотправляет RTP/RTCP frames по WSM binary protocol;- remote
MediaChannelпринимает RTP поreceiver_ssrc, декодирует video и рисует в canvas renderer; - ForceKeyFrame RTCP отправляется через WSM.
Fallback разрешен только если WebRTC не поднялся изначально в режиме auto или если выбран forced vg.mediaTransport=wsm. После уже успешного WebRTC video endpoint stale recovery не должен переключать video в WSM.
Reconnect и stale behavior
stateDiagram-v2
[*] --> Capturing
Capturing --> PublishingRTC: device_connect my=1 + WebRTC publish connected
PublishingRTC --> PublishStalled: ICE/connection disconnected
PublishStalled --> RepublishRTC: local publish recovery timer
RepublishRTC --> PublishingRTC: new RTCPeerConnection, same device_id
PublishingRTC --> Stopping: toggleCam(false) / leave
Stopping --> [*]
Для remote video после WebRTC restart нужно:
- создать новый
RTCPeerConnection; - повторить
webrtc_offerс тем же binding; - дождаться
webrtc_answer/ICE; - заново attach remote track к тому же UI slot;
- запросить keyframe через gateway;
- сохранить UI session, если server-side device не был отключен.
Текущие тайминги web-client:
restore_stale_session=trueпинает active local publish recovery через700ms;- local video/screen
failed- recovery через250ms; - local video/screen
disconnected- recovery через1800ms; - remote video
stalled/no inbound RTP progressзапускает restart через500ms; - unsuccessful remote restart остается
stalledи повторяется с backoff700ms,1200ms,2000ms,3200ms,5000ms,8000ms; - лимит remote restart-попыток -
6; - после
restore_stale_session=trueremote video sessions вstalled/errorполучают retry через900ms.
Invariants
- Камера публикуется через
device_params; media transport не создает device. device_idпринадлежит control lifecycle.author_ssrcиспользуется локальным publish path для отправки.receiver_ssrcиспользуется remote video subscribe path для приема.- Один remote video device должен иметь одну renderer/session pair.
- Reconnect media transport не должен создавать новую плитку UI.
toggleCam(false)должен остановить capture, media attach и отправитьdisconnect_device.- Повторный
device_connectдолжен быть идемпотентным. - WebRTC recovery после stale должен переиспользовать
sourceDevice, а не делать новыйdevice_params. - WSM fallback не должен включаться после уже успешного WebRTC video endpoint.
Diagnostics
Для video-инцидента в логах нужны:
device_id,client_id,device_type;author_ssrcилиreceiver_ssrc;- resolution, fps, codec profile;
- renderer/video element attach;
- WebRTC endpoint id,
rtc_node_id, offer/answer timing; - ICE/connection state;
- first RTP packet received на gateway/translator bridge;
- ForceKeyFrame/PLI sent;
- first keyframe received;
- video metadata/playing event;
- decoder configured/errors для WSM fallback;
- reconnect/restart reason.
Typical failures
- Черный экран сразу после подключения: проверить WebRTC answer/ICE, remote track, video metadata, first keyframe/PLI.
- Локальная камера работает, но удаленный участник видит черный экран: проверить local
device_connect my=1, WebRTC publish connected, first RTP на RTC node/Translator, remote subscribe. - После stale/reconnect видео не возвращается: проверить
restore_stale_session, remote restart retry/backoff, local publish recovery, без WSM fallback после active RTC. - Выключение/включение камеры плодит черные плитки: проверить идемпотентность remote
device_connect, очистку old renderer/session и связкуdevice_id + client_id + device_type. - Decode errors идут серией: для WebRTC проверить codec negotiation/keyframe/PLI; для WSM fallback дополнительно packet boundaries и WebCodecs decoder.