Skip to content

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 или forced vg.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 и повторяется с backoff 700ms, 1200ms, 2000ms, 3200ms, 5000ms, 8000ms;
  • лимит remote restart-попыток - 6;
  • после restore_stale_session=true remote 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.