Files
obs-streamer-tools-plugin/scripts/livekit-dev-room.py
shadowdaoandClaude Opus 5.5 7723082795
Build / Linux (ubuntu-24.04) (push) Successful in 1m8s
Build / macOS (macos-latest) (push) Successful in 1m9s
Build / macOS (macos-latest) (pull_request) Successful in 44s
Build / Windows (windows-latest) (push) Successful in 7m0s
Build / Windows (windows-latest) (pull_request) Successful in 4m14s
Build / Linux (ubuntu-24.04) (pull_request) Failing after 42m30s
feat(session): subscribe only to the slot's own camera and mic
Every plugin source connected with auto_subscribe on, so each one pulled
every camera in the room and discarded all but one. In the 2026-10-04
old-gods-of-appalachia show (8 sources, 7 cameras) each source carried
4-7 Mbps and all 8 hit congestion in the same instant: they share the
director's single downlink, so the SFU starved the cameras on screen.

The session now connects with auto_subscribe off and calls
setSubscribed(true) on exactly the wanted publications: at connect, on
onTrackPublished (re-sweeping the participant, since SDK 1.10.1 delivers
that event's publication null), and after a reconnect. Any subscription
it did not ask for is reported and handed back. Audio-only sources no
longer subscribe-then-disable video; they never subscribe to it.

The integration test gains a bystander camera that must never be
subscribed (fails with 3 unwanted subscriptions before this change) and
a full publisher leave/rejoin as a new participant session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 18:00:28 -07:00

86 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Mint LiveKit JWTs for the integration test against a local dev server.
Usage:
livekit-server --dev --bind 127.0.0.1 &
eval "$(python3 scripts/livekit-dev-room.py)"
ctest --test-dir build -R test_integration_livekit --output-on-failure
Prints shell `export` lines for the STPLUGIN_IT_* variables
core/tests/test_integration_livekit.cpp looks for. With no arguments it uses
`livekit-server --dev`'s built-in devkey/secret credentials.
Standard library only (hmac + hashlib + base64) -- there is deliberately no
pip install step here, so this runs anywhere the repo is checked out.
"""
import argparse
import base64
import hashlib
import hmac
import json
import os
import time
def b64url(raw: bytes) -> str:
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii")
def mint(api_key: str, api_secret: str, identity: str, room: str, *, publish: bool, subscribe: bool) -> str:
now = int(time.time())
header = {"alg": "HS256", "typ": "JWT"}
claims = {
"iss": api_key,
"sub": identity,
"name": identity,
"nbf": now - 10,
"exp": now + 3600,
"video": {
"room": room,
"roomJoin": True,
"canPublish": publish,
"canSubscribe": subscribe,
"canPublishData": False,
},
}
signing_input = f"{b64url(json.dumps(header, separators=(',', ':')).encode())}." \
f"{b64url(json.dumps(claims, separators=(',', ':')).encode())}"
signature = hmac.new(api_secret.encode(), signing_input.encode(), hashlib.sha256).digest()
return f"{signing_input}.{b64url(signature)}"
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--url", default=os.environ.get("LIVEKIT_URL", "ws://127.0.0.1:7880"))
parser.add_argument("--api-key", default=os.environ.get("LIVEKIT_API_KEY", "devkey"))
parser.add_argument("--api-secret", default=os.environ.get("LIVEKIT_API_SECRET", "secret"))
parser.add_argument("--room", default="obs-plugin-it")
parser.add_argument("--publisher-identity", default="cam-test")
parser.add_argument("--subscriber-identity", default="obs:obs-plugin-it:test")
parser.add_argument("--bystander-identity", default="cam-bystander")
args = parser.parse_args()
publish_token = mint(args.api_key, args.api_secret, args.publisher_identity, args.room,
publish=True, subscribe=False)
# Deliberately the same grant shape the real server mints for the plugin
# (apps/server/src/obs/plugin.routes.ts -> mintCaptionsToken): subscribe
# only, never publish.
subscribe_token = mint(args.api_key, args.api_secret, args.subscriber_identity, args.room,
publish=False, subscribe=True)
# A second camera in the same room that the session must NOT subscribe
# to -- the selective-subscription check.
bystander_token = mint(args.api_key, args.api_secret, args.bystander_identity, args.room,
publish=True, subscribe=False)
print(f'export STPLUGIN_IT_URL="{args.url}"')
print(f'export STPLUGIN_IT_PUBLISH_TOKEN="{publish_token}"')
print(f'export STPLUGIN_IT_SUBSCRIBE_TOKEN="{subscribe_token}"')
print(f'export STPLUGIN_IT_PUBLISHER_IDENTITY="{args.publisher_identity}"')
print(f'export STPLUGIN_IT_BYSTANDER_TOKEN="{bystander_token}"')
if __name__ == "__main__":
main()