Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,18 @@ TS6_CHANNEL_PASSWORD=
# - STREAM_MODE: 1 = request-based (server forwards notifyjoinstreamrequest
# for each viewer and the bot replies via respondjoinstreamrequest);
# 0 = auto-accept.
# - STREAM_VIEWER_LIMIT: -1 = unlimited (TS3 convention). 0 is interpreted
# by some TS6 builds as "no viewers allowed", which silently drops every
# join attempt.
# - STREAM_VIEWER_LIMIT: each viewer spins up its own VP8 encoder
# (~280 MB RSS at 720p30) so unlimited can OOM a small host. The default
# of 4 is conservative; bump it once you've measured memory under load.
# -1 = unlimited (TS3 convention). 0 is interpreted by some TS6 builds
# as "no viewers allowed", which silently drops every join attempt -
# don't use 0.
# - STREAM_BITRATE: hint to the server (kbps); 4608 mirrors the default the
# TS6 client uses for "high quality" screen share.
STREAM_BITRATE=4608
STREAM_ACCESSIBILITY=0
STREAM_MODE=1
STREAM_VIEWER_LIMIT=-1
STREAM_VIEWER_LIMIT=4

# --- WebRTC ICE servers ----------------------------------------------------
# STUN exposes the bot's public-NAT'd IP as a server-reflexive candidate;
Expand Down
9 changes: 7 additions & 2 deletions src/ts6_stream_bot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,13 @@ class Settings(BaseSettings):
),
)
STREAM_VIEWER_LIMIT: int = Field(
default=-1,
description="Max viewers; -1 = unlimited (TS3 convention).",
default=4,
description=(
"Max concurrent viewers. Each viewer spins up its own VP8 encoder "
"(~280 MB RSS at 720p30); without a cap the bot can OOM the host "
"before the operator notices. -1 = unlimited (TS3 convention) - "
"only set this if you've measured memory under load."
),
)

# --- WebRTC ICE -------------------------------------------------------
Expand Down
1 change: 0 additions & 1 deletion src/ts6_stream_bot/pipeline/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ async def _allocate_stream(self) -> None:
return

signaling = StreamSignaling(self._ts3_client)
signaling.register_stream_notifications()

capture_config = VideoCaptureConfig(
display=settings.DISPLAY,
Expand Down
11 changes: 7 additions & 4 deletions src/ts6_stream_bot/pipeline/stream_signaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,13 @@ def _stream_from_params(p: dict[str, str], *, audio_key: str) -> ActiveStream:

# --- outbound commands ------------------------------------------------

def register_stream_notifications(self) -> None:
"""Subscribe to the server-side notifications we need. Idempotent."""
for event in ("channel", "server", "textchannel"):
self._client.send_command(build_command("servernotifyregister", {"event": event}))
# Note: voice clients (which is what we are) receive stream-related
# notifications automatically by virtue of being in the channel; the
# ``servernotifyregister`` command is for ServerQuery clients only and
# the TS6 server replies with ``error id=516 invalid client type`` if
# we send it. The ts6-manager port we started from registered three
# events here because it ran as a ServerQuery client - that doesn't
# apply to us.

def send_setup_stream(
self,
Expand Down
9 changes: 0 additions & 9 deletions tests/test_stream_signaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,6 @@ def test_send_stream_stop_and_remove_client() -> None:
assert rm_cmd.params["clid"] == "42"


def test_register_stream_notifications_subscribes_to_three_events() -> None:
client = _FakeClient()
sig = StreamSignaling(client) # type: ignore[arg-type]
sig.register_stream_notifications()

events = [parse_command(c).params["event"] for c in client.sent]
assert events == ["channel", "server", "textchannel"]


# --- chaining with prior on_command ---------------------------------------


Expand Down
Loading