mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-05-26 07:08:11 +00:00
566 lines
26 KiB
Python
Executable File
566 lines
26 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import asyncio
|
|
import json
|
|
import uuid
|
|
import requests
|
|
from urllib.parse import quote
|
|
import websockets
|
|
from datetime import datetime
|
|
|
|
CONFERENCE_ID = "75047680642749"
|
|
CONFERENCE_URL = f"https://telemost.yandex.ru/j/{CONFERENCE_ID}"
|
|
API_BASE = "https://cloud-api.yandex.ru/telemost_front/v2/telemost"
|
|
|
|
session = requests.Session()
|
|
|
|
def log(msg, level="INFO"):
|
|
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
log_msg = f"[{timestamp}] [{level}] {msg}"
|
|
print(log_msg)
|
|
|
|
def generate_uuid():
|
|
return str(uuid.uuid4())
|
|
|
|
def get_connection_info(display_name):
|
|
url = f"{API_BASE}/conferences/{quote(CONFERENCE_URL, safe='')}/connection"
|
|
params = {
|
|
"next_gen_media_platform_allowed": "true",
|
|
"display_name": display_name,
|
|
"waiting_room_supported": "true"
|
|
}
|
|
|
|
headers = {
|
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:149.0) Gecko/20100101 Firefox/149.0",
|
|
"Accept": "*/*",
|
|
"content-type": "application/json",
|
|
"Client-Instance-Id": generate_uuid(),
|
|
"X-Telemost-Client-Version": "187.1.0",
|
|
"idempotency-key": generate_uuid(),
|
|
"Origin": "https://telemost.yandex.ru",
|
|
"Referer": "https://telemost.yandex.ru/"
|
|
}
|
|
|
|
log(f"GET {url}", "HTTP")
|
|
log(f"Params: {params}", "DEBUG")
|
|
|
|
response = session.get(url, params=params, headers=headers)
|
|
response.raise_for_status()
|
|
|
|
log(f"Response: {response.status_code}", "HTTP")
|
|
log(f"Cookies: {list(session.cookies.keys())}", "DEBUG")
|
|
|
|
return response.json()
|
|
|
|
def get_participants_list(peer_ids=None):
|
|
url = f"{API_BASE}/conferences/{quote(CONFERENCE_URL, safe='')}/request-states"
|
|
|
|
headers = {
|
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:149.0) Gecko/20100101 Firefox/149.0",
|
|
"Accept": "*/*",
|
|
"content-type": "application/json",
|
|
"Client-Instance-Id": generate_uuid(),
|
|
"idempotency-key": generate_uuid(),
|
|
"Origin": "https://telemost.yandex.ru",
|
|
"Referer": "https://telemost.yandex.ru/"
|
|
}
|
|
|
|
if peer_ids:
|
|
payload = {
|
|
"peers": [{"peer_id": pid} for pid in peer_ids],
|
|
"permissions": {},
|
|
"conference": {"version": -1}
|
|
}
|
|
else:
|
|
payload = {
|
|
"peers": [],
|
|
"permissions": {},
|
|
"conference": {"version": -1}
|
|
}
|
|
|
|
log(f"POST {url}", "HTTP")
|
|
log(f"Payload: {json.dumps(payload)[:100]}...", "DEBUG")
|
|
|
|
response = session.post(url, json=payload, headers=headers)
|
|
response.raise_for_status()
|
|
|
|
log(f"Response: {response.status_code}", "HTTP")
|
|
|
|
result = response.json()
|
|
log(f"Response body: {json.dumps(result)[:500]}...", "DEBUG")
|
|
|
|
return result
|
|
|
|
async def collect_webrtc_info():
|
|
log(r"""
|
|
|
|
WebRTC Full Information Collector
|
|
Complete conference & peer analysis
|
|
by zowue for olc
|
|
|
|
""", "INFO")
|
|
|
|
info = {
|
|
"connection": {},
|
|
"participants": {},
|
|
"webrtc": {
|
|
"ice": {},
|
|
"sdp": {},
|
|
"datachannel": {},
|
|
"audio": {},
|
|
"video": {}
|
|
},
|
|
"server": {}
|
|
}
|
|
|
|
log("[1/5] Getting connection info...")
|
|
try:
|
|
conn_info = get_connection_info("InfoCollector")
|
|
|
|
cookies_dict = session.cookies.get_dict()
|
|
if cookies_dict:
|
|
log(f" :P Cookies: {', '.join(cookies_dict.keys())}", "INFO")
|
|
|
|
info["connection"] = {
|
|
"connection_type": conn_info.get("connection_type"),
|
|
"uri": conn_info.get("uri"),
|
|
"room_id": conn_info.get("room_id"),
|
|
"safe_room_id": conn_info.get("safe_room_id"),
|
|
"peer_id": conn_info.get("peer_id"),
|
|
"session_id": conn_info.get("session_id"),
|
|
"peer_session_id": conn_info.get("peer_session_id"),
|
|
"expiration_time": conn_info.get("expiration_time"),
|
|
"conference_limit": conn_info.get("conference_limit"),
|
|
"media_platform": conn_info.get("media_platform")
|
|
}
|
|
|
|
client_config = conn_info.get("client_configuration", {})
|
|
info["connection"]["client_config"] = {
|
|
"media_server_url": client_config.get("media_server_url"),
|
|
"service_name": client_config.get("service_name"),
|
|
"session_timeout_ms": client_config.get("goloom_session_open_ms"),
|
|
"reconnect_wait_ms": client_config.get("wait_time_to_reconnect_ms")
|
|
}
|
|
|
|
log(f" :P Room: {info['connection']['room_id']}")
|
|
log(f" :P Peer: {info['connection']['peer_id']}")
|
|
log(f" :P Limit: {info['connection']['conference_limit']}")
|
|
except Exception as e:
|
|
log(f" X Error: {e}", "ERROR")
|
|
return info
|
|
|
|
log("\n[2/5] Connecting to WebSocket...")
|
|
try:
|
|
ws_url = client_config.get("media_server_url")
|
|
log(f" -> WS URL: {ws_url}", "DEBUG")
|
|
ws = await websockets.connect(ws_url)
|
|
|
|
hello_msg = {
|
|
"uid": generate_uuid(),
|
|
"hello": {
|
|
"participantMeta": {"name": "InfoCollector", "role": "SPEAKER", "sendAudio": False, "sendVideo": False},
|
|
"participantAttributes": {"name": "InfoCollector", "role": "SPEAKER"},
|
|
"sendAudio": False,
|
|
"sendVideo": False,
|
|
"sendSharing": False,
|
|
"participantId": conn_info["peer_id"],
|
|
"roomId": conn_info["room_id"],
|
|
"serviceName": "telemost",
|
|
"credentials": conn_info["credentials"],
|
|
"capabilitiesOffer": {
|
|
"offerAnswerMode": ["SEPARATE"],
|
|
"initialSubscriberOffer": ["ON_HELLO"],
|
|
"slotsMode": ["FROM_CONTROLLER"],
|
|
"simulcastMode": ["STATIC"],
|
|
"selfVadStatus": ["FROM_SERVER"],
|
|
"dataChannelSharing": ["TO_RTP"]
|
|
},
|
|
"sdkInfo": {"implementation": "python", "version": "1.0.0", "userAgent": "InfoCollector"},
|
|
"sdkInitializationId": generate_uuid(),
|
|
"disablePublisher": False,
|
|
"disableSubscriber": False
|
|
}
|
|
}
|
|
|
|
await ws.send(json.dumps(hello_msg))
|
|
log(" :P Connected")
|
|
log(f" -> Sent hello message", "DEBUG")
|
|
|
|
log("\n[3/5] Collecting participants...")
|
|
info["participants"]["list"] = []
|
|
collected_peer_ids = set()
|
|
|
|
log("\n[4/5] Collecting WebRTC details...")
|
|
|
|
for i in range(25):
|
|
try:
|
|
data = json.loads(await asyncio.wait_for(ws.recv(), timeout=1.0))
|
|
|
|
msg_type = next((k for k in data.keys() if k != "uid"), "unknown")
|
|
log(f" <- WS message #{i+1}: {msg_type}", "WS")
|
|
|
|
if "updateDescription" in data:
|
|
update_desc = data["updateDescription"]
|
|
log(f" -> updateDescription: {json.dumps(update_desc)[:200]}...", "DEBUG")
|
|
|
|
if "description" in update_desc:
|
|
for desc_item in update_desc["description"]:
|
|
peer_id = desc_item.get("id")
|
|
|
|
if peer_id:
|
|
log(f" -> Found peer in updateDescription: {peer_id}", "DEBUG")
|
|
|
|
if peer_id not in collected_peer_ids:
|
|
collected_peer_ids.add(peer_id)
|
|
|
|
meta = desc_item.get("meta", {})
|
|
peer_data = {
|
|
"peer_id": peer_id,
|
|
"display_name": meta.get("name"),
|
|
"role": meta.get("role"),
|
|
"send_audio": meta.get("sendAudio"),
|
|
"send_video": meta.get("sendVideo"),
|
|
"send_sharing": desc_item.get("sendSharing", False)
|
|
}
|
|
|
|
info["participants"]["list"].append(peer_data)
|
|
audio_status = "ON" if peer_data['send_audio'] else "OFF"
|
|
video_status = "ON" if peer_data['send_video'] else "OFF"
|
|
log(f" :P {peer_data['display_name']} (A:{audio_status} V:{video_status})")
|
|
|
|
await ws.send(json.dumps({"uid": data["uid"], "ack": {"status": {"code": "OK"}}}))
|
|
|
|
if "serverHello" in data:
|
|
server_hello = data["serverHello"]
|
|
|
|
if "capabilitiesAnswer" in server_hello:
|
|
caps = server_hello["capabilitiesAnswer"]
|
|
info["webrtc"]["capabilities"] = {
|
|
"offerAnswerMode": caps.get("offerAnswerMode"),
|
|
"initialSubscriberOffer": caps.get("initialSubscriberOffer"),
|
|
"slotsMode": caps.get("slotsMode"),
|
|
"simulcastMode": caps.get("simulcastMode"),
|
|
"selfVadStatus": caps.get("selfVadStatus"),
|
|
"dataChannelSharing": caps.get("dataChannelSharing"),
|
|
"videoEncoderConfig": caps.get("videoEncoderConfig"),
|
|
"dataChannelVideoCodec": caps.get("dataChannelVideoCodec"),
|
|
"bandwidthLimitationReason": caps.get("bandwidthLimitationReason"),
|
|
"publisherVp9": caps.get("publisherVp9"),
|
|
"svcMode": caps.get("svcMode")
|
|
}
|
|
|
|
if "servingComponents" in server_hello:
|
|
info["server"]["components"] = []
|
|
for comp in server_hello["servingComponents"]:
|
|
info["server"]["components"].append({
|
|
"type": comp.get("type"),
|
|
"host": comp.get("host"),
|
|
"version": comp.get("version")
|
|
})
|
|
|
|
if "rtcConfiguration" in server_hello:
|
|
rtc_config = server_hello["rtcConfiguration"]
|
|
info["webrtc"]["ice"]["servers"] = []
|
|
|
|
for server in rtc_config.get("iceServers", []):
|
|
info["webrtc"]["ice"]["servers"].append({
|
|
"urls": server.get("urls"),
|
|
"has_credentials": bool(server.get("credential"))
|
|
})
|
|
|
|
if "pingPongConfiguration" in server_hello:
|
|
ping_config = server_hello["pingPongConfiguration"]
|
|
info["webrtc"]["ping"] = {
|
|
"interval_ms": ping_config.get("pingInterval"),
|
|
"ack_timeout_ms": ping_config.get("ackTimeout")
|
|
}
|
|
|
|
if "telemetryConfiguration" in server_hello:
|
|
telem_config = server_hello["telemetryConfiguration"]
|
|
info["webrtc"]["telemetry"] = {
|
|
"sending_interval_ms": telem_config.get("sendingInterval")
|
|
}
|
|
|
|
await ws.send(json.dumps({"uid": data["uid"], "ack": {"status": {"code": "OK"}}}))
|
|
log(" :P Server hello")
|
|
|
|
if "subscriberSdpOffer" in data:
|
|
sdp = data["subscriberSdpOffer"]["sdp"]
|
|
|
|
info["webrtc"]["sdp"]["raw"] = sdp
|
|
info["webrtc"]["sdp"]["lines"] = sdp.count('\r\n')
|
|
|
|
info["webrtc"]["audio"]["codecs"] = []
|
|
info["webrtc"]["video"]["codecs"] = []
|
|
info["webrtc"]["datachannel"]["supported"] = False
|
|
|
|
for line in sdp.split('\r\n'):
|
|
if line.startswith('a=rtpmap:') and 'm=audio' in sdp[:sdp.find(line)]:
|
|
parts = line.split()
|
|
if len(parts) >= 2:
|
|
codec_info = parts[1].split('/')
|
|
info["webrtc"]["audio"]["codecs"].append({
|
|
"name": codec_info[0],
|
|
"rate": int(codec_info[1]) if len(codec_info) > 1 else None,
|
|
"channels": int(codec_info[2]) if len(codec_info) > 2 else None
|
|
})
|
|
|
|
if line.startswith('a=rtpmap:') and 'm=video' in sdp[:sdp.find(line)]:
|
|
parts = line.split()
|
|
if len(parts) >= 2:
|
|
codec_info = parts[1].split('/')
|
|
codec_name = codec_info[0].upper()
|
|
if codec_name not in ['RTX', 'RED', 'ULPFEC']:
|
|
if codec_name not in [c["name"] for c in info["webrtc"]["video"]["codecs"]]:
|
|
info["webrtc"]["video"]["codecs"].append({
|
|
"name": codec_name,
|
|
"rate": int(codec_info[1]) if len(codec_info) > 1 else None
|
|
})
|
|
|
|
if 'm=application' in line and 'SCTP' in line:
|
|
info["webrtc"]["datachannel"]["supported"] = True
|
|
|
|
if 'sctp-port:' in line:
|
|
info["webrtc"]["datachannel"]["sctp_port"] = int(line.split(':')[1].strip())
|
|
|
|
if 'max-message-size:' in line:
|
|
size = int(line.split(':')[1].strip())
|
|
info["webrtc"]["datachannel"]["max_message_size"] = size
|
|
info["webrtc"]["datachannel"]["max_message_size_mb"] = size / 1024 / 1024
|
|
|
|
if line.startswith('a=fmtp:') and 'opus' in sdp[max(0, sdp.find(line)-200):sdp.find(line)].lower():
|
|
params = line.split(':', 1)[1].strip().split(';')
|
|
info["webrtc"]["audio"]["opus_params"] = {}
|
|
for param in params:
|
|
if '=' in param:
|
|
key, val = param.strip().split('=')
|
|
info["webrtc"]["audio"]["opus_params"][key] = val
|
|
|
|
if line.startswith('a=extmap:'):
|
|
if "rtp_extensions" not in info["webrtc"]:
|
|
info["webrtc"]["rtp_extensions"] = []
|
|
ext_info = line.split()[1]
|
|
info["webrtc"]["rtp_extensions"].append(ext_info)
|
|
|
|
await ws.send(json.dumps({"uid": data["uid"], "ack": {"status": {"code": "OK"}}}))
|
|
log(" :P SDP analyzed")
|
|
|
|
except asyncio.TimeoutError:
|
|
log(f" -> Timeout on message #{i+1}, continuing...", "DEBUG")
|
|
break
|
|
except Exception as e:
|
|
log(f" X Error processing message: {e}", "ERROR")
|
|
break
|
|
|
|
info["participants"]["count"] = len(info["participants"]["list"])
|
|
|
|
await ws.close()
|
|
log(" :P Closed")
|
|
|
|
log("\n -> Fetching all participants via API...")
|
|
try:
|
|
all_participants = get_participants_list()
|
|
|
|
log(f" -> API returned: peers={len(all_participants.get('peers', []))}, permissions={bool(all_participants.get('permissions'))}", "DEBUG")
|
|
|
|
if "peers" in all_participants and all_participants["peers"]:
|
|
log(f" :P Found {len(all_participants['peers'])} participants via API")
|
|
|
|
for peer in all_participants["peers"]:
|
|
peer_id = peer.get("peer_id")
|
|
if peer_id and peer_id not in collected_peer_ids:
|
|
peer_data = {
|
|
"peer_id": peer_id,
|
|
"peer_type": peer.get("peer_type"),
|
|
"send_audio": None,
|
|
"send_video": None,
|
|
"send_sharing": None
|
|
}
|
|
|
|
if "state" in peer and "user_data" in peer["state"]:
|
|
user_data = peer["state"]["user_data"]
|
|
peer_data["display_name"] = user_data.get("display_name")
|
|
peer_data["role"] = user_data.get("role")
|
|
peer_data["uid"] = user_data.get("uid")
|
|
if "avatar_placeholder" in user_data:
|
|
peer_data["avatar"] = user_data["avatar_placeholder"]
|
|
|
|
info["participants"]["list"].append(peer_data)
|
|
collected_peer_ids.add(peer_id)
|
|
log(f" :P {peer_data.get('display_name', 'Unknown')} ({peer_data.get('role', 'N/A')})")
|
|
|
|
info["participants"]["count"] = len(info["participants"]["list"])
|
|
else:
|
|
log(f" X No peers in API response", "WARN")
|
|
|
|
if "permissions" in all_participants:
|
|
info["conference"] = {"permissions": all_participants["permissions"]}
|
|
if "conference" in all_participants:
|
|
info["conference"]["state"] = all_participants["conference"].get("state")
|
|
|
|
except Exception as e:
|
|
log(f" X API request failed: {e}", "ERROR")
|
|
|
|
log(f"\n :P Total participants: {info['participants']['count']}")
|
|
|
|
except Exception as e:
|
|
log(f" X Error: {e}", "ERROR")
|
|
|
|
return info
|
|
|
|
def print_full_report(info):
|
|
print("CONNECTION INFO")
|
|
conn = info["connection"]
|
|
print(f"Type: {conn.get('connection_type')}")
|
|
print(f"URI: {conn.get('uri')}")
|
|
print(f"Room ID: {conn.get('room_id')}")
|
|
print(f"Peer ID: {conn.get('peer_id')}")
|
|
print(f"Session ID: {conn.get('session_id')}")
|
|
print(f"Media Platform: {conn.get('media_platform')}")
|
|
print(f"Max Participants: {conn.get('conference_limit')}")
|
|
|
|
if "client_config" in conn:
|
|
cfg = conn["client_config"]
|
|
print(f"\nClient Configuration:")
|
|
print(f" Media Server: {cfg.get('media_server_url')}")
|
|
print(f" Session Timeout: {cfg.get('session_timeout_ms')}ms ({cfg.get('session_timeout_ms', 0)/1000/60:.1f}min)")
|
|
print(f" Reconnect Wait: {cfg.get('reconnect_wait_ms')}ms")
|
|
|
|
print("PARTICIPANTS")
|
|
if "list" in info["participants"] and info["participants"]["list"]:
|
|
for i, peer in enumerate(info["participants"]["list"], 1):
|
|
print(f"\n{i}. {peer.get('display_name', 'Unknown')}")
|
|
print(f" Peer ID: {peer.get('peer_id')}")
|
|
print(f" Role: {peer.get('role')}")
|
|
if peer.get('uid'):
|
|
print(f" UID: {peer.get('uid')}")
|
|
print(f" Audio: {peer.get('send_audio')}")
|
|
print(f" Video: {peer.get('send_video')}")
|
|
print(f" Sharing: {peer.get('send_sharing')}")
|
|
if "avatar" in peer:
|
|
avatar = peer["avatar"]
|
|
print(f" Avatar: {avatar.get('abbreviation')} ({avatar.get('background_color')})")
|
|
print(f"\nTotal: {info['participants'].get('count', 0)} participants")
|
|
else:
|
|
print("No participants in conference")
|
|
|
|
if "conference" in info and "permissions" in info["conference"]:
|
|
print("\n" + "=" * 70)
|
|
print("CONFERENCE PERMISSIONS")
|
|
print("=" * 70)
|
|
perms = info["conference"]["permissions"]
|
|
print(f"Version: {perms.get('version')}")
|
|
|
|
if "public_role_permissions" in perms:
|
|
print("\nRole Permissions:")
|
|
for role_perm in perms["public_role_permissions"]:
|
|
role = role_perm.get("role")
|
|
allowed = role_perm.get("allowed", [])
|
|
print(f" {role}: {', '.join(allowed)}")
|
|
|
|
if "personal_allowed" in perms:
|
|
print(f"\nPersonal: {', '.join(perms['personal_allowed'])}")
|
|
|
|
if "state" in info["conference"]:
|
|
state = info["conference"]["state"]
|
|
print("\nConference State:")
|
|
print(f" Access Level: {state.get('access_level')}")
|
|
print(f" Recording: {state.get('local_recording_allowed')}")
|
|
print(f" Cloud Recording: {state.get('cloud_recording_allowed')}")
|
|
print(f" Chat: {state.get('chat_allowed')}")
|
|
print(f" Control: {state.get('control_allowed')}")
|
|
print(f" Broadcast: {state.get('broadcast_allowed')}")
|
|
|
|
print("WEBRTC CAPABILITIES")
|
|
if "capabilities" in info["webrtc"]:
|
|
caps = info["webrtc"]["capabilities"]
|
|
print(f"Offer/Answer Mode: {caps.get('offerAnswerMode')}")
|
|
print(f"Initial Subscriber Offer: {caps.get('initialSubscriberOffer')}")
|
|
print(f"Slots Mode: {caps.get('slotsMode')}")
|
|
print(f"Simulcast Mode: {caps.get('simulcastMode')}")
|
|
print(f"VAD Status: {caps.get('selfVadStatus')}")
|
|
print(f"DataChannel Sharing: {caps.get('dataChannelSharing')}")
|
|
print(f"Video Encoder Config: {caps.get('videoEncoderConfig')}")
|
|
print(f"DC Video Codec: {caps.get('dataChannelVideoCodec')}")
|
|
print(f"Bandwidth Limitation: {caps.get('bandwidthLimitationReason')}")
|
|
print(f"Publisher VP9: {caps.get('publisherVp9')}")
|
|
print(f"SVC Mode: {caps.get('svcMode')}")
|
|
|
|
print("AUDIO")
|
|
if "codecs" in info["webrtc"]["audio"]:
|
|
print("Codecs:")
|
|
for codec in info["webrtc"]["audio"]["codecs"]:
|
|
print(f" - {codec['name']}: {codec.get('rate', 'N/A')}Hz, {codec.get('channels', 'N/A')} channels")
|
|
|
|
if "opus_params" in info["webrtc"]["audio"]:
|
|
print("\nOpus Parameters:")
|
|
for key, val in info["webrtc"]["audio"]["opus_params"].items():
|
|
print(f" {key}: {val}")
|
|
|
|
print("VIDEO")
|
|
if "codecs" in info["webrtc"]["video"]:
|
|
print("Codecs:")
|
|
for codec in info["webrtc"]["video"]["codecs"]:
|
|
print(f" - {codec['name']}: {codec.get('rate', 'N/A')}Hz")
|
|
|
|
print("DATACHANNEL")
|
|
dc = info["webrtc"]["datachannel"]
|
|
print(f"Supported: {':P YES' if dc.get('supported') else 'X NO'}")
|
|
if dc.get("supported"):
|
|
print(f"SCTP Port: {dc.get('sctp_port')}")
|
|
print(f"Max Message Size: {dc.get('max_message_size_mb', 0):.0f}MB ({dc.get('max_message_size', 0):,} bytes)")
|
|
print(f"Note: Actual limit is 8KB due to server fragmentation")
|
|
|
|
print("ICE/NETWORK")
|
|
if "servers" in info["webrtc"]["ice"]:
|
|
stun_count = sum(1 for s in info["webrtc"]["ice"]["servers"] if any('stun:' in u for u in s.get("urls", [])))
|
|
turn_count = sum(1 for s in info["webrtc"]["ice"]["servers"] if any('turn:' in u for u in s.get("urls", [])))
|
|
|
|
print(f"STUN Servers: {stun_count}")
|
|
print(f"TURN Servers: {turn_count}")
|
|
print("\nServers:")
|
|
for server in info["webrtc"]["ice"]["servers"]:
|
|
for url in server.get("urls", []):
|
|
cred_status = "with credentials" if server.get("has_credentials") else "no credentials"
|
|
print(f" - {url} ({cred_status})")
|
|
|
|
if "ping" in info["webrtc"]:
|
|
ping = info["webrtc"]["ping"]
|
|
print(f"\nPing Configuration:")
|
|
print(f" Interval: {ping.get('interval_ms')}ms")
|
|
print(f" ACK Timeout: {ping.get('ack_timeout_ms')}ms")
|
|
|
|
if "rtp_extensions" in info["webrtc"]:
|
|
print(f"\nRTP Extensions: {len(info['webrtc']['rtp_extensions'])}")
|
|
for ext in info["webrtc"]["rtp_extensions"][:5]:
|
|
print(f" - {ext}")
|
|
|
|
print("SERVER COMPONENTS")
|
|
if "components" in info["server"]:
|
|
for comp in info["server"]["components"]:
|
|
print(f"{comp.get('type'):20} {comp.get('host'):30} v{comp.get('version')}")
|
|
|
|
if "telemetry" in info["webrtc"]:
|
|
telem = info["webrtc"]["telemetry"]
|
|
print(f"\nTelemetry:")
|
|
print(f" Sending Interval: {telem.get('sending_interval_ms')}ms")
|
|
|
|
print("SDP STATISTICS")
|
|
if "lines" in info["webrtc"]["sdp"]:
|
|
print(f"Total SDP Lines: {info['webrtc']['sdp']['lines']}")
|
|
print(f"SDP Size: {len(info['webrtc']['sdp'].get('raw', ''))} bytes")
|
|
|
|
async def main():
|
|
log(f"Starting WebRTC info collection...")
|
|
|
|
info = await collect_webrtc_info()
|
|
|
|
log("\n[5/5] Generating report...\n")
|
|
print_full_report(info)
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
asyncio.run(main())
|
|
except KeyboardInterrupt:
|
|
log("\n\nInfo collection interrupted.", "WARN")
|