"""WebRTC ICE server config: STUN + optional TURN for cross-network voice preview.""" from __future__ import annotations import base64 import hashlib import hmac import time import config STUN_URL = "stun:stun.l.google.com:19302" def _turn_credentials() -> tuple[str, str] | None: """Return (username, credential) for TURN, or None when TURN is not configured.""" if not config.TURN_URLS: return None if config.TURN_SECRET: expiry = int(time.time()) + config.TURN_CREDENTIAL_TTL username = f"{expiry}:ai-video" credential = base64.b64encode( hmac.new( config.TURN_SECRET.encode("utf-8"), username.encode("utf-8"), hashlib.sha1, ).digest() ).decode("utf-8") return username, credential if config.TURN_USERNAME and config.TURN_PASSWORD: return config.TURN_USERNAME, config.TURN_PASSWORD return None def client_ice_servers() -> list[dict]: """ICE servers for browser RTCPeerConnection (JSON-serializable).""" servers: list[dict] = [{"urls": STUN_URL}] creds = _turn_credentials() if not creds: return servers username, credential = creds for url in config.TURN_URLS: servers.append({"urls": url, "username": username, "credential": credential}) return servers def aiortc_ice_servers() -> list: """ICE servers for backend SmallWebRTCConnection / aiortc.""" from aiortc import RTCIceServer servers = [RTCIceServer(urls=STUN_URL)] creds = _turn_credentials() if not creds: return servers username, credential = creds for url in config.TURN_URLS: servers.append( RTCIceServer(urls=url, username=username, credential=credential) ) return servers