319 lines
11 KiB
Python
319 lines
11 KiB
Python
"""
|
||
Клиент для работы с 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()
|
||
|