""" Клиент для работы с Janus Gateway API. Параллельная реализация с ion-sfu для сравнения. """ import requests import logging from typing import Dict, Any, Optional from django.conf import settings logger = logging.getLogger(__name__) class JanusClient: """Клиент для взаимодействия с Janus Gateway.""" def __init__(self, base_url: Optional[str] = None): """ Инициализация клиента Janus. Args: base_url: Базовый URL Janus API (например, http://localhost:8088/janus) """ self.base_url = base_url or getattr(settings, 'JANUS_HTTP_URL', 'http://localhost:8088/janus') self.session_id = None self.plugin_handle_id = None def create_session(self) -> Dict[str, Any]: """ Создать новую сессию Janus. Returns: Словарь с информацией о сессии, включая session_id """ try: response = requests.post( self.base_url, json={"janus": "create", "transaction": self._generate_transaction_id()}, timeout=5 ) response.raise_for_status() data = response.json() if data.get('janus') == 'success': self.session_id = data.get('data', {}).get('id') logger.info(f"Janus session created: {self.session_id}") return data else: raise Exception(f"Failed to create Janus session: {data}") except Exception as e: logger.error(f"Error creating Janus session: {e}") raise def attach_plugin(self, plugin: str = "janus.plugin.videoroom") -> Dict[str, Any]: """ Подключить плагин к сессии. Args: plugin: Имя плагина (по умолчанию videoroom) Returns: Словарь с информацией о плагине, включая handle_id """ if not self.session_id: raise Exception("No active session. Call create_session() first.") try: response = requests.post( f"{self.base_url}/{self.session_id}", json={ "janus": "attach", "plugin": plugin, "transaction": self._generate_transaction_id() }, timeout=5 ) response.raise_for_status() data = response.json() if data.get('janus') == 'success': self.plugin_handle_id = data.get('data', {}).get('id') logger.info(f"Plugin attached: {plugin}, handle: {self.plugin_handle_id}") return data else: raise Exception(f"Failed to attach plugin: {data}") except Exception as e: logger.error(f"Error attaching plugin: {e}") raise def create_room(self, room_id: int, description: str = "", publishers: int = 6, secret: str = "adminpwd") -> Dict[str, Any]: """ Создать новую видеокомнату. Args: room_id: ID комнаты description: Описание комнаты publishers: Максимальное количество publishers secret: Секретный ключ для управления комнатой Returns: Информация о созданной комнате """ if not self.plugin_handle_id: raise Exception("No plugin handle. Call attach_plugin() first.") try: response = requests.post( f"{self.base_url}/{self.session_id}/{self.plugin_handle_id}", json={ "janus": "message", "transaction": self._generate_transaction_id(), "body": { "request": "create", "room": room_id, "description": description, "publishers": publishers, "bitrate": 512000, "fir_freq": 10, "audiocodec": "opus", "videocodec": "vp8", "record": False, "rec_dir": "/recordings", "secret": secret # Секретный ключ для управления # НЕ передаём pin вообще - комната будет без пароля } }, timeout=5 ) response.raise_for_status() data = response.json() logger.info(f"Room created: {room_id}") return data except Exception as e: logger.error(f"Error creating room: {e}") raise def destroy_room(self, room_id: int, secret: str = "adminpwd") -> Dict[str, Any]: """ Удалить видеокомнату. Args: room_id: ID комнаты для удаления secret: Секретный ключ (должен совпадать с тем, что использовался при создании) Returns: Результат операции """ if not self.plugin_handle_id: raise Exception("No plugin handle. Call attach_plugin() first.") try: response = requests.post( f"{self.base_url}/{self.session_id}/{self.plugin_handle_id}", json={ "janus": "message", "transaction": self._generate_transaction_id(), "body": { "request": "destroy", "room": room_id, "secret": secret # Обязательно для удаления } }, timeout=5 ) response.raise_for_status() data = response.json() logger.info(f"Room destroyed: {room_id}") return data except Exception as e: logger.error(f"Error destroying room: {e}") raise def list_rooms(self) -> Dict[str, Any]: """ Получить список всех комнат. Returns: Список комнат """ if not self.plugin_handle_id: raise Exception("No plugin handle. Call attach_plugin() first.") try: response = requests.post( f"{self.base_url}/{self.session_id}/{self.plugin_handle_id}", json={ "janus": "message", "transaction": self._generate_transaction_id(), "body": { "request": "list" } }, timeout=5 ) response.raise_for_status() data = response.json() return data except Exception as e: logger.error(f"Error listing rooms: {e}") raise def get_room_info(self, room_id: int) -> Dict[str, Any]: """ Получить информацию о комнате. Args: room_id: ID комнаты Returns: Информация о комнате """ if not self.plugin_handle_id: raise Exception("No plugin handle. Call attach_plugin() first.") try: response = requests.post( f"{self.base_url}/{self.session_id}/{self.plugin_handle_id}", json={ "janus": "message", "transaction": self._generate_transaction_id(), "body": { "request": "exists", "room": room_id } }, timeout=5 ) response.raise_for_status() data = response.json() return data except Exception as e: logger.error(f"Error getting room info: {e}") raise def keepalive(self) -> Dict[str, Any]: """ Отправить keepalive для поддержания сессии. Returns: Результат операции """ if not self.session_id: raise Exception("No active session.") try: response = requests.post( f"{self.base_url}/{self.session_id}", json={ "janus": "keepalive", "transaction": self._generate_transaction_id() }, timeout=5 ) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error sending keepalive: {e}") raise def destroy_session(self) -> None: """Уничтожить сессию Janus.""" if not self.session_id: return try: requests.post( f"{self.base_url}/{self.session_id}", json={ "janus": "destroy", "transaction": self._generate_transaction_id() }, timeout=5 ) logger.info(f"Janus session destroyed: {self.session_id}") self.session_id = None self.plugin_handle_id = None except Exception as e: logger.error(f"Error destroying session: {e}") @staticmethod def _generate_transaction_id() -> str: """Генерировать уникальный ID транзакции.""" import random import string return ''.join(random.choices(string.ascii_letters + string.digits, k=12)) def __enter__(self): """Context manager entry.""" self.create_session() self.attach_plugin() return self def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit.""" self.destroy_session() # Вспомогательная функция для быстрого получения клиента def get_janus_client() -> JanusClient: """ Получить настроенный экземпляр JanusClient. Returns: JanusClient: Клиент для работы с Janus Gateway """ return JanusClient()