mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-05-26 07:08:11 +00:00
docs: normalize em dash to hyphen in Russian documentation and code comments
This commit is contained in:
@@ -127,7 +127,7 @@ cd olcrtc
|
||||
Введите Room ID:
|
||||
```
|
||||
|
||||
Для **jitsi** — полный URL комнаты в формате `https://host/room` (например `https://meet.cryptopro.ru/myroom`). Имя комнаты придумывается на лету, без регистрации. Подойдёт любой публичный или self-hosted Jitsi Meet.
|
||||
Для **jitsi** - полный URL комнаты в формате `https://host/room` (например `https://meet.cryptopro.ru/myroom`). Имя комнаты придумывается на лету, без регистрации. Подойдёт любой публичный или self-hosted Jitsi Meet.
|
||||
|
||||
Для **telemost** и **wbstream** - создай руму через сайт ([telemost](https://telemost.yandex.ru/), [wbstream](https://stream.wb.ru)) и вставь её ID.
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ openssl rand -hex 32
|
||||
|
||||
### jitsi + datachannel (рекомендуется)
|
||||
|
||||
Самый простой способ: используй любой self-hosted или публичный Jitsi Meet инстанс. Регистрация не нужна, имя комнаты выдумывается на лету. По умолчанию в примерах ниже — `meet.cryptopro.ru`, но подойдёт любой другой (`meet.jit.si`, свой self-hosted и т.п.).
|
||||
Самый простой способ: используй любой self-hosted или публичный Jitsi Meet инстанс. Регистрация не нужна, имя комнаты выдумывается на лету. По умолчанию в примерах ниже - `meet.cryptopro.ru`, но подойдёт любой другой (`meet.jit.si`, свой self-hosted и т.п.).
|
||||
|
||||
Создай YAML конфиг:
|
||||
|
||||
@@ -180,13 +180,13 @@ data: data
|
||||
./build/olcrtc-linux-amd64 server.yaml
|
||||
```
|
||||
|
||||
Сервер сам присоединится к комнате (в качестве участника без камеры/микрофона) и будет ждать, пока клиент тоже зайдёт. Без второго участника Jicofo не выдаёт session-initiate — это особенность Jitsi.
|
||||
Сервер сам присоединится к комнате (в качестве участника без камеры/микрофона) и будет ждать, пока клиент тоже зайдёт. Без второго участника Jicofo не выдаёт session-initiate - это особенность Jitsi.
|
||||
|
||||
### wbstream + vp8channel (альтернатива)
|
||||
|
||||
Создай руму через сайт [wbstream](https://stream.wb.ru) и вставь её ID в `room.id`.
|
||||
|
||||
`wbstream + datachannel` **не работает** в обычном guest flow — WB Stream выдаёт токены с `canPublishData=false`, и DC не маршрутизирует данные. Для обычного использования выбирай `vp8channel`.
|
||||
`wbstream + datachannel` **не работает** в обычном guest flow - WB Stream выдаёт токены с `canPublishData=false`, и DC не маршрутизирует данные. Для обычного использования выбирай `vp8channel`.
|
||||
|
||||
Создай YAML конфиг:
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
**Jitsi:** datachannel стабильно проходит - реализован поверх colibri-ws bridge channel и шлёт байты через `EndpointMessage{raw}` broadcast. Подходит для self-hosted и публичных Jitsi Meet инстансов без аутентификации (`https://meet.cryptopro.ru/...`, `https://meet.jit.si/...` и т.п.). Видео-транспорты (vp8channel, seichannel, videochannel) экспонируют sendable VideoTrack через pion PeerConnection после Jingle session-accept, но Jicofo требует дополнительных протокольных шагов (LastN, ReceiverVideoConstraints, source-add) для маршрутизации видео - поэтому они помечены `~` .
|
||||
|
||||
**Jitsi + seichannel — отдельная оговорка.** SEI NAL-юниты идут пассажиром в H.264 видеопотоке, а Jicofo на self-hosted инстансах (например `meet.cryptopro.ru`) периодически режет/откладывает upstream видео когда ресивера в комнате формально нет - для нас это выглядит как `seichannel ack timeout` при формально живом PeerConnection. В steady-state транспорт работает, но e2e матрица помечает его `Unstable` (флаппит): зелёного и красного результата в CI достаточно, тест suite на этом не валится. Для надёжной передачи данных через jitsi предпочтительнее `datachannel` или `vp8channel`.
|
||||
**Jitsi + seichannel - отдельная оговорка.** SEI NAL-юниты идут пассажиром в H.264 видеопотоке, а Jicofo на self-hosted инстансах (например `meet.cryptopro.ru`) периодически режет/откладывает upstream видео когда ресивера в комнате формально нет - для нас это выглядит как `seichannel ack timeout` при формально живом PeerConnection. В steady-state транспорт работает, но e2e матрица помечает его `Unstable` (флаппит): зелёного и красного результата в CI достаточно, тест suite на этом не валится. Для надёжной передачи данных через jitsi предпочтительнее `datachannel` или `vp8channel`.
|
||||
|
||||
**Рекомендуемая комбинация: `jitsi + datachannel`** — стабильно работает на любом self-hosted или публичном Jitsi Meet (например `meet.cryptopro.ru`), не требует регистрации, простая руму создания. Альтернатива: `wbstream + vp8channel` — стабильно для коммерческих сценариев, не требует специальных прав.
|
||||
**Рекомендуемая комбинация: `jitsi + datachannel`** - стабильно работает на любом self-hosted или публичном Jitsi Meet (например `meet.cryptopro.ru`), не требует регистрации, простая руму создания. Альтернатива: `wbstream + vp8channel` - стабильно для коммерческих сценариев, не требует специальных прав.
|
||||
|
||||
Скорость по убыванию: `datachannel` > `vp8channel` > `seichannel` > `videochannel`
|
||||
|
||||
@@ -191,7 +191,7 @@ transport. Используй одинаковые traffic-настройки н
|
||||
|
||||
### wbstream + datachannel (не работает в обычном guest flow)
|
||||
|
||||
WB Stream DataChannel **не работает** в обычном guest flow — WB Stream выдаёт токены с `canPublishData=false`, и DC не маршрутизирует данные. Этот режим помечен как expected fail в E2E тестах. Для обычного использования выбирай `vp8channel`, `seichannel` или `videochannel`.
|
||||
WB Stream DataChannel **не работает** в обычном guest flow - WB Stream выдаёт токены с `canPublishData=false`, и DC не маршрутизирует данные. Этот режим помечен как expected fail в E2E тестах. Для обычного использования выбирай `vp8channel`, `seichannel` или `videochannel`.
|
||||
|
||||
```yaml
|
||||
# room ID нужно создать вручную через https://stream.wb.ru
|
||||
|
||||
@@ -222,7 +222,7 @@ data: data
|
||||
olcrtc://jitsi?datachannel@https://meet.cryptopro.ru/myroom#d823fa01cb3e0609b67322f7cf984c4ee2e4ce2e294936fc24ef38c9e59f4799$RU / olc free sub
|
||||
```
|
||||
|
||||
`<RoomID>` для jitsi — полный URL комнаты в формате `https://host/room` (или `host/room`). Поддерживается любой self-hosted Jitsi Meet инстанс без аутентификации; для публичных серверов вроде `meet.jit.si` тот же формат.
|
||||
`<RoomID>` для jitsi - полный URL комнаты в формате `https://host/room` (или `host/room`). Поддерживается любой self-hosted Jitsi Meet инстанс без аутентификации; для публичных серверов вроде `meet.jit.si` тот же формат.
|
||||
|
||||
### Эквивалент YAML
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// expressed via the RoomCreator interface.
|
||||
//
|
||||
// The "none" auth provider passes a caller-supplied URL+Token through
|
||||
// unchanged — this is the path that sing-box and other downstream consumers
|
||||
// unchanged - this is the path that sing-box and other downstream consumers
|
||||
// take when they want to use olcrtc as a generic LiveKit/Goolom/Jitsi
|
||||
// transport without any service-specific behaviour baked in.
|
||||
package auth
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
//
|
||||
// URL is the signaling endpoint (e.g. wss://livekit.example/). Token is the
|
||||
// access token (LiveKit JWT, Goolom session credential, etc). Extra is for
|
||||
// engine-specific bits that don't fit the common shape — engines should not
|
||||
// engine-specific bits that don't fit the common shape - engines should not
|
||||
// rely on it being populated unless their paired auth provider documents it.
|
||||
type Credentials struct {
|
||||
URL string
|
||||
@@ -56,7 +56,7 @@ type Provider interface {
|
||||
// Engine reports which engine this auth provider feeds.
|
||||
Engine() string
|
||||
// DefaultServiceURL returns the well-known service URL for this provider
|
||||
// (e.g. "https://stream.wb.ru"). Returns "" if no default exists — in that
|
||||
// (e.g. "https://stream.wb.ru"). Returns "" if no default exists - in that
|
||||
// case the caller must supply -url explicitly.
|
||||
DefaultServiceURL() string
|
||||
// Issue obtains credentials for the given room.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// Public Jitsi Meet servers do not require authentication for guest access;
|
||||
// the only "credentials" the engine needs are the host+room pair extracted
|
||||
// from a user-supplied room URL. This provider does no HTTP at all — it just
|
||||
// from a user-supplied room URL. This provider does no HTTP at all - it just
|
||||
// parses the URL and forwards host+room to the engine via auth.Credentials.
|
||||
//
|
||||
// Supported RoomURL forms:
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
// Optional URL path prefixes (e.g. "/jitsi") are preserved as part of the
|
||||
// host when present, so deployments behind a path-mounted reverse proxy work
|
||||
// transparently — the j library accepts any host string the WebSocket dial
|
||||
// transparently - the j library accepts any host string the WebSocket dial
|
||||
// can resolve.
|
||||
package jitsi
|
||||
|
||||
@@ -51,7 +51,7 @@ func (Provider) DefaultServiceURL() string { return defaultServiceURL }
|
||||
//
|
||||
// The URL field of the returned Credentials carries the Jitsi host (e.g.
|
||||
// "meet.example.com"); the room name lives in Extra under CredentialKeyRoom.
|
||||
// Token is unused — Jitsi guest access requires no token.
|
||||
// Token is unused - Jitsi guest access requires no token.
|
||||
func (Provider) Issue(_ context.Context, cfg auth.Config) (auth.Credentials, error) {
|
||||
host, room, err := parseRoomURL(cfg.RoomURL)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// It fetches the connection metadata (media server URL, peer ID, room ID,
|
||||
// signing credentials) the Goolom engine needs to join a conference.
|
||||
//
|
||||
// Telemost does not expose an API to create rooms — they originate in the
|
||||
// Yandex UI — so this provider does not implement auth.RoomCreator.
|
||||
// Telemost does not expose an API to create rooms - they originate in the
|
||||
// Yandex UI - so this provider does not implement auth.RoomCreator.
|
||||
package telemost
|
||||
|
||||
import (
|
||||
|
||||
@@ -142,7 +142,7 @@ func RunWithReady(ctx context.Context, cfg Config, onReady func()) error {
|
||||
// (e.g. handshake timeout against a wedged seichannel transport)
|
||||
// without ever calling Close on the carrier link, leaving our MUC
|
||||
// presence behind as a ghost participant in the next test that
|
||||
// joined the same room. shutdown is nil-safe — it skips fields
|
||||
// joined the same room. shutdown is nil-safe - it skips fields
|
||||
// that bringUpLink hadn't populated yet.
|
||||
defer c.shutdown()
|
||||
|
||||
@@ -204,7 +204,7 @@ func (c *Client) bringUpLink(
|
||||
}
|
||||
// Carrier callback fires after the link is back up. If handshake
|
||||
// still fails it usually means the server hasn't completed its
|
||||
// own reinstall yet — keep the listener up and wait for either
|
||||
// own reinstall yet - keep the listener up and wait for either
|
||||
// another callback or a future liveness loss to re-trigger.
|
||||
c.handleReconnect(ctx, cfg, cancel, "carrier")
|
||||
})
|
||||
@@ -287,8 +287,8 @@ func openControlStreamTimeout(
|
||||
// resolveDeviceID returns the device ID to send in CLIENT_HELLO.
|
||||
//
|
||||
// Precedence:
|
||||
// 1. Explicit deviceID arg (Config.DeviceID) — used verbatim.
|
||||
// 2. Persistent file at path (Config.DeviceIDPath) — read if it exists,
|
||||
// 1. Explicit deviceID arg (Config.DeviceID) - used verbatim.
|
||||
// 2. Persistent file at path (Config.DeviceIDPath) - read if it exists,
|
||||
// otherwise generated and written for future runs.
|
||||
// 3. Random UUID per run when both inputs are empty.
|
||||
func resolveDeviceID(deviceID, path string) (string, error) {
|
||||
@@ -392,12 +392,12 @@ func (c *Client) retryHandshake(ctx context.Context, cfg Config, cancel context.
|
||||
}
|
||||
// Don't fail the whole process on liveness reconnect: the carrier
|
||||
// rebuild may take dozens of seconds (e.g. ICE restart on a flaky
|
||||
// network). Keep the SOCKS5 listener open and wait — handleSocks5
|
||||
// network). Keep the SOCKS5 listener open and wait - handleSocks5
|
||||
// will return host-unreachable to clients until we recover. For
|
||||
// carrier-driven reconnects the callback fires after the link is
|
||||
// already up, so a missed handshake is more suspicious; cap it.
|
||||
if reason == "carrier" && attempt >= 5 {
|
||||
logger.Warnf("client reconnect: exhausted %d handshake attempts (reason=%s) — keeping listener up", attempt, reason)
|
||||
logger.Warnf("client reconnect: exhausted %d handshake attempts (reason=%s) - keeping listener up", attempt, reason)
|
||||
return
|
||||
}
|
||||
select {
|
||||
|
||||
@@ -407,7 +407,7 @@ func pumpReader(
|
||||
}
|
||||
|
||||
// isExpectedShutdownErr filters errors that just mean "we asked the conn
|
||||
// to stop" — deadline expirations from our SetDeadline kick, EOF from the
|
||||
// to stop" - deadline expirations from our SetDeadline kick, EOF from the
|
||||
// peer half-closing, etc.
|
||||
func isExpectedShutdownErr(err error) bool {
|
||||
if err == nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ var (
|
||||
realStress = flag.Bool( //nolint:gochecknoglobals // package-level state intentional
|
||||
"olcrtc.stress",
|
||||
false,
|
||||
"run real provider stress matrix (bulk transfer + sustained echo) — requires -olcrtc.real-e2e",
|
||||
"run real provider stress matrix (bulk transfer + sustained echo) - requires -olcrtc.real-e2e",
|
||||
)
|
||||
realStressBulkDuration = flag.Duration( //nolint:gochecknoglobals // package-level state intentional
|
||||
"olcrtc.stress-bulk-duration",
|
||||
@@ -183,7 +183,7 @@ func runRealE2EStressCase(t *testing.T, carrierName, transportName, roomURL, ech
|
||||
}
|
||||
|
||||
goroutinesAfter := runtime.NumGoroutine()
|
||||
// Allow some slack — pion/quic spawn helpers that take time to wind down
|
||||
// Allow some slack - pion/quic spawn helpers that take time to wind down
|
||||
// after Close, but a real leak shows up as tens of extra goroutines.
|
||||
const goroutineLeakSlack = 30
|
||||
if goroutinesAfter > goroutinesBefore+goroutineLeakSlack {
|
||||
@@ -204,7 +204,7 @@ func runRealE2EStressCase(t *testing.T, carrierName, transportName, roomURL, ech
|
||||
// MiB/s; videochannel/seichannel ~25 KB/s through 256-byte qr-encoded
|
||||
// frames at 25 FPS). An asynchronous writer outruns a slow transport,
|
||||
// fills muxconn / SOCKS / RTP-track buffers, and the deadlocked pipe
|
||||
// eventually trips a TCP-write deadline — which is not a real bug, just
|
||||
// eventually trips a TCP-write deadline - which is not a real bug, just
|
||||
// the natural consequence of pumping into a slow pipe with no flow
|
||||
// control. Request-response naturally rate-limits to the transport's
|
||||
// actual round-trip throughput, which is what we want to measure.
|
||||
@@ -251,10 +251,10 @@ func streamPatternForDuration(conn net.Conn, duration time.Duration, chunkSize i
|
||||
}
|
||||
|
||||
type echoStats struct {
|
||||
count int
|
||||
lost int
|
||||
p50, p95, p99 time.Duration
|
||||
maxLatency time.Duration
|
||||
count int
|
||||
lost int
|
||||
p50, p95, p99 time.Duration
|
||||
maxLatency time.Duration
|
||||
}
|
||||
|
||||
// sustainedEcho writes payloads of size `payloadSize` and waits for them to
|
||||
|
||||
@@ -110,7 +110,7 @@ const (
|
||||
// known to flap: it sometimes succeeds and sometimes fails for
|
||||
// reasons outside our control (third-party server load, lossy SFU
|
||||
// paths, etc.). The matrix runner records the outcome but does
|
||||
// not fail the test either way. Use this sparingly — prefer
|
||||
// not fail the test either way. Use this sparingly - prefer
|
||||
// ExpectPass / ExpectFail when the behaviour is deterministic.
|
||||
realE2EExpectUnstable
|
||||
)
|
||||
@@ -234,7 +234,7 @@ type memoryStream struct {
|
||||
// video tracks through a real (in-process) WebRTC stack.
|
||||
//
|
||||
// streamCtx is owned by the stream itself (cancelled in Close), not
|
||||
// by the short-lived ctx that Connect receives — the video
|
||||
// by the short-lived ctx that Connect receives - the video
|
||||
// transport's connectCtx fires its deferred cancel as soon as
|
||||
// streamTransport.Connect returns, which would otherwise tear down
|
||||
// the async SDP negotiation goroutine before it can find its peer.
|
||||
@@ -385,7 +385,7 @@ func (s *memoryStream) SetVideoTrackHandler(cb func(*webrtc.TrackRemote, *webrtc
|
||||
// transceiver that AddTrack on the peer side will create even if
|
||||
// SetVideoTrackHandler arrives before AddVideoTrack.
|
||||
if _, err := s.ensurePC(); err != nil {
|
||||
// e2e helper: swallow — the failure surfaces on the next
|
||||
// e2e helper: swallow - the failure surfaces on the next
|
||||
// AddVideoTrack/Connect path that actually needs the PC.
|
||||
_ = err
|
||||
}
|
||||
@@ -813,13 +813,13 @@ func realRoomURL(ctx context.Context, t *testing.T, carrierName string) string {
|
||||
t.Skip("skip wbstream real e2e: set -olcrtc.real-wbstream-room to an existing room ID")
|
||||
return ""
|
||||
case "jitsi":
|
||||
// Jitsi has no notion of "creating" a room — names are conjured
|
||||
// Jitsi has no notion of "creating" a room - names are conjured
|
||||
// on first join. The default flag points at meet.cryptopro.ru
|
||||
// by default. When the flag is left at its default value, a
|
||||
// per-process random suffix is appended
|
||||
// to the slug: two participants share a single room by design (one
|
||||
// pair, one shared key), so any third participant — including another
|
||||
// concurrent test process with the same shared key — would corrupt
|
||||
// pair, one shared key), so any third participant - including another
|
||||
// concurrent test process with the same shared key - would corrupt
|
||||
// the wire protocol on both sides. Users overriding the flag are
|
||||
// trusted to manage room uniqueness themselves.
|
||||
_ = ctx
|
||||
|
||||
@@ -30,7 +30,7 @@ type Capabilities struct {
|
||||
VideoTrack bool
|
||||
}
|
||||
|
||||
// Credentials are produced by an auth provider — duplicated here to avoid an
|
||||
// Credentials are produced by an auth provider - duplicated here to avoid an
|
||||
// import cycle between engine and auth.
|
||||
type Credentials struct {
|
||||
URL string
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Package goolom implements an engine.Session backed by the Goolom SFU
|
||||
// signaling protocol. Goolom is the proprietary SFU developed for Yandex
|
||||
// Telemost; the on-wire protocol — capabilities offer, separated subscriber
|
||||
// Telemost; the on-wire protocol - capabilities offer, separated subscriber
|
||||
// and publisher PeerConnections, ack/pong keepalive, slots-based subscribe
|
||||
// model — is what this engine speaks.
|
||||
// model - is what this engine speaks.
|
||||
//
|
||||
// HTTP auth (room-info lookup, telemetry referer, etc.) lives in the auth
|
||||
// package; this engine consumes a media-server WebSocket URL plus the
|
||||
@@ -35,10 +35,10 @@ const (
|
||||
wsReadTimeout = 60 * time.Second
|
||||
wsHandshakeTimeout = 15 * time.Second
|
||||
|
||||
keyUID = "uid"
|
||||
keyDescription = "description"
|
||||
keyPcSeq = "pcSeq"
|
||||
keyName = "name"
|
||||
keyUID = "uid"
|
||||
keyDescription = "description"
|
||||
keyPcSeq = "pcSeq"
|
||||
keyName = "name"
|
||||
stateTerminated = "terminated"
|
||||
|
||||
credentialKeyRoomID = "roomID"
|
||||
@@ -88,7 +88,7 @@ type Session struct {
|
||||
peerID string
|
||||
roomID string
|
||||
credentials string
|
||||
roomURL string // referer for telemetry — opaque to the engine
|
||||
roomURL string // referer for telemetry - opaque to the engine
|
||||
telemetryReferer string
|
||||
refresh func(ctx context.Context) (engine.Credentials, error)
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestReconnectWindowEnforcesCapWithinWindow(t *testing.T) {
|
||||
|
||||
// TestResetPeerClearsBindingForNewPeer covers fix 032151b: after an
|
||||
// upper-layer handshake failure the supervisor calls ResetPeer, and the
|
||||
// next peer in the room must be allowed to latch — not blocked by the
|
||||
// next peer in the room must be allowed to latch - not blocked by the
|
||||
// previously-latched (now stale) endpoint.
|
||||
//
|
||||
// jitsi_test.go has no coverage for this path.
|
||||
@@ -102,7 +102,7 @@ func TestResetPeerClearsBindingForNewPeer(t *testing.T) {
|
||||
frameA := makeBridgeFrameForEpoch(t, 0x1111, 0, []byte("from-A"))
|
||||
js.deliverBridgeMessage(makeBridgeMessageFrom("peerA", map[string]any{rawFieldKey: frameA}), true)
|
||||
|
||||
// Peer B tries while A still owns the latch — must be dropped.
|
||||
// Peer B tries while A still owns the latch - must be dropped.
|
||||
frameB1 := makeBridgeFrameForEpoch(t, 0x2222, 0, []byte("from-B-blocked"))
|
||||
js.deliverBridgeMessage(makeBridgeMessageFrom("peerB", map[string]any{rawFieldKey: frameB1}), true)
|
||||
|
||||
@@ -196,7 +196,7 @@ func TestChurnPeerEpochChanges(t *testing.T) {
|
||||
t.Fatalf("stale frames delivered: %d (filter regression)", staleDelivered.Load())
|
||||
}
|
||||
if delivered.Load() == 0 {
|
||||
t.Fatal("no frames delivered at all — filter is too aggressive")
|
||||
t.Fatal("no frames delivered at all - filter is too aggressive")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ type bridgeOutbound struct {
|
||||
|
||||
// New creates a new Jitsi engine session.
|
||||
//
|
||||
// cfg.URL carries the Jitsi host (e.g. "meet.cryptopro.ru") — populated by the
|
||||
// cfg.URL carries the Jitsi host (e.g. "meet.cryptopro.ru") - populated by the
|
||||
// jitsi auth provider after parsing the user-supplied room URL. cfg.Extra
|
||||
// must contain the room name under the "room" key.
|
||||
func New(_ context.Context, cfg engine.Config) (engine.Session, error) {
|
||||
@@ -364,7 +364,7 @@ func (s *Session) videoTrackHandler() func(*webrtc.TrackRemote, *webrtc.RTPRecei
|
||||
|
||||
// negotiatePC builds the pion PeerConnection, applies Jicofo's offer,
|
||||
// answers it and registers all the per-side wiring (DTLS state, ICE
|
||||
// callbacks, transceiver direction). It's branchy on purpose — Jingle
|
||||
// callbacks, transceiver direction). It's branchy on purpose - Jingle
|
||||
// negotiation has many discrete steps that can fail and each step
|
||||
// belongs to the same logical operation, so splitting it into helpers
|
||||
// would obscure the wire order rather than clarify it.
|
||||
@@ -376,7 +376,7 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session) error {
|
||||
|
||||
// pion auto-registers a default interceptor chain (sender reports,
|
||||
// receiver reports, NACK, etc.) when none is supplied. Several of
|
||||
// those probe the DTLS transport on a tick — until DTLS comes up
|
||||
// those probe the DTLS transport on a tick - until DTLS comes up
|
||||
// (which can take seconds against Jitsi's STUN-only path, or never
|
||||
// in pathological cases) they spam logs with
|
||||
// "the DTLS transport has not started yet". JVB performs its own
|
||||
@@ -423,7 +423,7 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session) error {
|
||||
|
||||
// When sending video, AddTrack already creates the video m-line (sendonly).
|
||||
// When only receiving, an explicit recvonly transceiver is required so the
|
||||
// SDP answer includes a video m-line — without it JVB does not set up a
|
||||
// SDP answer includes a video m-line - without it JVB does not set up a
|
||||
// video forwarding path and ICE stalls. Mirrors the j library reference CLI:
|
||||
// AddTrack and AddTransceiverFromKind(video,recvonly) are mutually exclusive
|
||||
// in Plan B; using both produces a malformed SDP.
|
||||
@@ -469,13 +469,13 @@ func (s *Session) negotiatePC(ctx context.Context, jSess *j.Session) error {
|
||||
// (trickle ICE) and source-add (other participants' SSRCs) the moment
|
||||
// it sees us reply to session-initiate. If we started the drain loop
|
||||
// only after Accept and SendSourceAdd, those stanzas would queue in
|
||||
// the 64-slot channel while RTP — which travels straight over UDP/TURN
|
||||
// and reaches us in tens of ms — arrives first. Pion then drops the
|
||||
// the 64-slot channel while RTP - which travels straight over UDP/TURN
|
||||
// and reaches us in tens of ms - arrives first. Pion then drops the
|
||||
// peer's RTP as "unhandled SSRC, media section has an explicit SSRC"
|
||||
// because HandleSourceAdd hasn't grafted the SSRC onto the remote SDP
|
||||
// yet. The peer never produces an OnTrack callback, our handshake
|
||||
// never gets an ACK, and the tunnel dies. Starting the consumer first
|
||||
// closes that race window — any source-add Jicofo emits is picked up
|
||||
// closes that race window - any source-add Jicofo emits is picked up
|
||||
// the instant it lands on the wire.
|
||||
s.wg.Add(1)
|
||||
go s.trickleDrainLoop(pc, neg, jSess.LowLevel().Stanzas())
|
||||
@@ -939,7 +939,7 @@ func (s *Session) peerLatchAccepts(from string) bool {
|
||||
}
|
||||
|
||||
// decodeRaw extracts the bytes from an EndpointMessage produced by the j
|
||||
// library's BridgeSendRaw helper. Mirrors the unexported colibri.DecodeRaw —
|
||||
// library's BridgeSendRaw helper. Mirrors the unexported colibri.DecodeRaw -
|
||||
// the j library's BridgeMessage type alias keeps the necessary fields public,
|
||||
// but the helper itself lives in an internal package.
|
||||
func decodeRaw(m j.BridgeMessage) []byte {
|
||||
@@ -963,14 +963,14 @@ func decodeRaw(m j.BridgeMessage) []byte {
|
||||
//
|
||||
// 1. Mark the session closed so send/recv loops drop new work.
|
||||
// 2. Close the pion PeerConnection (stops media, sends DTLS bye). This
|
||||
// mirrors jvbJingleSession.close() in lib-jitsi-meet — note that
|
||||
// mirrors jvbJingleSession.close() in lib-jitsi-meet - note that
|
||||
// graceful leave there does NOT send Jingle session-terminate; Jicofo
|
||||
// learns of the departure from the MUC presence-unavailable stanza
|
||||
// and only then frees the JVB bridge slot.
|
||||
// 3. Close the underlying j.Session, which closes the colibri-ws bridge,
|
||||
// performs the MUC presence-unavailable handshake (LeaveMUCWait
|
||||
// waits for Prosody to echo our own unavailable presence — the
|
||||
// XMPP-level equivalent of XMPPEvents.MUC_LEFT — with a 5s cap),
|
||||
// waits for Prosody to echo our own unavailable presence - the
|
||||
// XMPP-level equivalent of XMPPEvents.MUC_LEFT - with a 5s cap),
|
||||
// and only then tears down the websocket.
|
||||
// 4. Cancel the supervisor context and wait for goroutines.
|
||||
//
|
||||
@@ -979,7 +979,7 @@ func decodeRaw(m j.BridgeMessage) []byte {
|
||||
// stops replying to our session-terminate IQ. TerminateWait then ate its
|
||||
// 3s budget and we still left ghost participants behind. lib-jitsi-meet
|
||||
// avoids this entirely by relying on MUC presence as the single source of
|
||||
// truth for departure — Prosody's MUC layer is far more reliable than
|
||||
// truth for departure - Prosody's MUC layer is far more reliable than
|
||||
// Jicofo's IQ handler under load.
|
||||
func (s *Session) Close() error {
|
||||
if !s.closed.CompareAndSwap(false, true) {
|
||||
@@ -1200,7 +1200,7 @@ func (s *Session) CanSend() bool {
|
||||
return false
|
||||
}
|
||||
if s.onData == nil && s.onPeerData == nil {
|
||||
// pure video mode — readiness driven by PC connection state
|
||||
// pure video mode - readiness driven by PC connection state
|
||||
s.pcMu.Lock()
|
||||
ready := s.pc != nil && s.pc.ConnectionState() == webrtc.PeerConnectionStateConnected
|
||||
s.pcMu.Unlock()
|
||||
@@ -1234,7 +1234,7 @@ func (s *Session) GetBufferedAmount() uint64 {
|
||||
//
|
||||
// Tracks added before Connect are sent as part of the session-accept SDP
|
||||
// (so Jicofo announces them to other participants automatically). Tracks
|
||||
// added afterwards are attached to the live PeerConnection — Jitsi's
|
||||
// added afterwards are attached to the live PeerConnection - Jitsi's
|
||||
// source-add flow is not yet implemented in this engine, so late tracks
|
||||
// will only be visible on the next reconnect.
|
||||
func (s *Session) AddVideoTrack(track webrtc.TrackLocal) error {
|
||||
|
||||
@@ -52,7 +52,7 @@ const (
|
||||
|
||||
// frameBufPool recycles plaintext buffers between Push (decrypts a wire
|
||||
// frame into a buffer) and Read (consumes the buffer fully then returns
|
||||
// it). It is global so all Conn instances share the same hot cache —
|
||||
// it). It is global so all Conn instances share the same hot cache -
|
||||
// most clients in the same process talk to a handful of peers, and
|
||||
// per-Conn pools fragment the warm set unnecessarily.
|
||||
var frameBufPool = sync.Pool{ //nolint:gochecknoglobals // intentional process-wide buffer pool
|
||||
|
||||
@@ -328,7 +328,7 @@ func (s *Server) reinstallSession(dead *smux.Session) {
|
||||
|
||||
s.sessMu.Lock()
|
||||
if s.session != dead {
|
||||
// Someone else already reinstalled — discard our build.
|
||||
// Someone else already reinstalled - discard our build.
|
||||
s.sessMu.Unlock()
|
||||
_ = newSess.Close()
|
||||
_ = newConn.Close()
|
||||
@@ -487,7 +487,7 @@ func (s *Server) getPeerSession(peerID string) *peerSession {
|
||||
}
|
||||
|
||||
// serve drives the smux Accept loop. The first accepted stream on a given
|
||||
// smux session is the control stream — the handshake runs there. Subsequent
|
||||
// smux session is the control stream - the handshake runs there. Subsequent
|
||||
// streams are tunnel streams and proxy traffic.
|
||||
func (s *Server) serve(ctx context.Context) {
|
||||
if s.peerLn != nil {
|
||||
|
||||
@@ -362,7 +362,7 @@ func TestHandleStreamDispatchAfterConnect(t *testing.T) {
|
||||
req, err := json.Marshal(ConnectRequest{
|
||||
Cmd: testConnectCmd,
|
||||
Addr: testConnectAddr,
|
||||
Port: 1, // unreachable port — dispatch will fail dial and exit
|
||||
Port: 1, // unreachable port - dispatch will fail dial and exit
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal() error = %v", err)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package common provides building blocks shared by the video-track based
|
||||
// transports (videochannel, seichannel) — fragment/reassembly, ack waiters,
|
||||
// transports (videochannel, seichannel) - fragment/reassembly, ack waiters,
|
||||
// and per-peer random IDs. vp8channel does its own KCP-based framing and
|
||||
// only consumes RandomID.
|
||||
package common
|
||||
@@ -208,7 +208,7 @@ func (a *AckRegistry) Unregister(seq uint32) {
|
||||
}
|
||||
|
||||
// Resolve delivers crc to the waiter for seq, if present. A missing waiter
|
||||
// is silently ignored — the sender has already moved on.
|
||||
// is silently ignored - the sender has already moved on.
|
||||
func (a *AckRegistry) Resolve(seq, crc uint32) {
|
||||
a.mu.Lock()
|
||||
waiter := a.waiters[seq]
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestReassemblerStressShuffledFragments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if dupCount == 0 {
|
||||
t.Fatal("test injected duplicates but reassembler reported none — duplicate path not exercised")
|
||||
t.Fatal("test injected duplicates but reassembler reported none - duplicate path not exercised")
|
||||
}
|
||||
t.Logf("delivered %d/%d messages, observed %d duplicates", len(delivered), messages, dupCount)
|
||||
}
|
||||
@@ -137,8 +137,8 @@ func TestReassemblerConcurrentPushIsSafe(t *testing.T) {
|
||||
r.Push(common.Fragment{
|
||||
Seq: seq,
|
||||
CRC: crc,
|
||||
TotalLen: uint32(len(p)), //nolint:gosec // bounded
|
||||
FragIdx: uint16(idx), //nolint:gosec // bounded
|
||||
TotalLen: uint32(len(p)), //nolint:gosec // bounded
|
||||
FragIdx: uint16(idx), //nolint:gosec // bounded
|
||||
FragTotal: uint16(len(raw)), //nolint:gosec // bounded
|
||||
Payload: raw[idx],
|
||||
})
|
||||
|
||||
@@ -135,7 +135,7 @@ func New(ctx context.Context, cfg transport.Config) (transport.Transport, error)
|
||||
}
|
||||
stream := &engineVideoSession{session: session, vt: vt}
|
||||
|
||||
// Stream/track IDs must be unique per peer — Jitsi rejects session-accept
|
||||
// Stream/track IDs must be unique per peer - Jitsi rejects session-accept
|
||||
// when msid collides with another participant in the conference.
|
||||
track, err := webrtc.NewTrackLocalStaticSample(
|
||||
webrtc.RTPCodecCapability{
|
||||
@@ -542,4 +542,3 @@ func decodeTransportFrame(data []byte) (transportFrame, error) {
|
||||
return transportFrame{}, ErrUnexpectedFrameType
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package transport defines transport abstractions and registry.
|
||||
//
|
||||
// A transport encodes byte payloads onto a carrier (engine) primitive — either
|
||||
// A transport encodes byte payloads onto a carrier (engine) primitive - either
|
||||
// a reliable byte stream (datachannel) or a video track (videochannel,
|
||||
// seichannel, vp8channel). Transport-specific tuning lives in per-transport
|
||||
// Options types; the common configuration shared by every transport lives in
|
||||
@@ -41,7 +41,7 @@ type Transport interface {
|
||||
Features() Features
|
||||
// Reconnect asks the underlying carrier (engine) to tear down and
|
||||
// re-establish the SFU connection. Upper layers call this when a
|
||||
// liveness probe declares the link dead — useful when the engine has
|
||||
// liveness probe declares the link dead - useful when the engine has
|
||||
// not yet noticed silent packet loss.
|
||||
Reconnect(reason string)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ type streamTransport struct {
|
||||
sendMu sync.Mutex
|
||||
startWriter sync.Once
|
||||
fragAcks *fragAckTracker
|
||||
reassembler *common.Reassembler
|
||||
reassembler *common.Reassembler
|
||||
videoW int
|
||||
videoH int
|
||||
videoFPS int
|
||||
@@ -650,7 +650,7 @@ func (p *streamTransport) handleInboundFrame(frame transportFrame) {
|
||||
p.sendAck(frame.seq, frame.crc, frame.fragIdx)
|
||||
case common.ResultPartial, common.ResultDuplicate:
|
||||
// Every fragment we successfully decoded gets acked, including
|
||||
// duplicates — under retransmits the sender may have lost the
|
||||
// duplicates - under retransmits the sender may have lost the
|
||||
// earlier ack and is waiting on this one.
|
||||
p.sendAck(frame.seq, frame.crc, frame.fragIdx)
|
||||
case common.ResultIgnore:
|
||||
|
||||
@@ -122,7 +122,7 @@ func runChaosLoopback(t *testing.T, msgs [][]byte, cfg chaosCfg, timeout time.Du
|
||||
|
||||
var droppedAB, droppedBA atomic.Uint64
|
||||
go chaosPump(t, stop, a2b, rtB, cfg, &droppedAB)
|
||||
// Return path stays clean by default — KCP ACKs must come back reliably
|
||||
// Return path stays clean by default - KCP ACKs must come back reliably
|
||||
// for fair loss measurement; loss on one direction is enough to stress.
|
||||
go chaosPump(t, stop, b2a, rtA, chaosCfg{}, &droppedBA)
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestKCPSurvivesModeratePacketLoss(t *testing.T) {
|
||||
dur, dropped := runChaosLoopback(t, msgs, chaosCfg{lossRatio: 0.10, seed: 0xC0FFEE}, 20*time.Second)
|
||||
t.Logf("delivered %d msgs in %s with %d packets dropped (10%% loss)", len(msgs), dur, dropped)
|
||||
if dropped == 0 {
|
||||
t.Fatal("chaos pump did not drop any packets — loss injection broken")
|
||||
t.Fatal("chaos pump did not drop any packets - loss injection broken")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func TestKCPRecoversFromBurstLoss(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestKCPThroughputBaseline establishes a perfect-channel throughput floor.
|
||||
// Not an assertion — if this number regresses meaningfully on the same
|
||||
// Not an assertion - if this number regresses meaningfully on the same
|
||||
// hardware, something changed in KCP options (window size, MTU, tick).
|
||||
func TestKCPThroughputBaseline(t *testing.T) {
|
||||
if testing.Short() {
|
||||
|
||||
@@ -143,7 +143,7 @@ func New(ctx context.Context, cfg transport.Config) (transport.Transport, error)
|
||||
}
|
||||
stream := &engineVideoSession{session: session, vt: vt}
|
||||
|
||||
// Stream/track IDs must be unique per peer — Jitsi rejects session-accept
|
||||
// Stream/track IDs must be unique per peer - Jitsi rejects session-accept
|
||||
// when msid collides with another participant in the conference.
|
||||
track, err := webrtc.NewTrackLocalStaticSample(
|
||||
webrtc.RTPCodecCapability{
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
// conn wraps a Session as a net.Conn.
|
||||
// Read is backed by an io.Pipe fed by the engine's OnData callback.
|
||||
// Write calls Session.Send.
|
||||
// Deadlines are not supported — callers should use context cancellation.
|
||||
// Deadlines are not supported - callers should use context cancellation.
|
||||
type conn struct {
|
||||
s *Session
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package olcrtc exposes olcrtc as an embeddable Go library.
|
||||
//
|
||||
// Typical usage — obtain a [net.Conn]-compatible handle and dial:
|
||||
// Typical usage - obtain a [net.Conn]-compatible handle and dial:
|
||||
//
|
||||
// sess, err := olcrtc.New(ctx, olcrtc.Config{
|
||||
// Engine: "livekit",
|
||||
@@ -9,7 +9,7 @@
|
||||
// })
|
||||
// if err != nil { ... }
|
||||
// conn, err := sess.Dial(ctx) // blocks until WebRTC data channel is ready
|
||||
// // conn implements net.Conn — pass it to sing-box / any io.ReadWriter consumer
|
||||
// // conn implements net.Conn - pass it to sing-box / any io.ReadWriter consumer
|
||||
//
|
||||
// Built-in auth providers (jitsi, telemost, wbstream):
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user