""" Django views для работы с Janus Gateway. Параллельная реализация с ion-sfu. """ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from django.conf import settings from django.utils import timezone from .models import VideoRoom, VideoParticipant, VideoCallLog from .serializers import VideoRoomSerializer from .janus_client import get_janus_client, JanusClient from .permissions import IsVideoRoomParticipant import logging logger = logging.getLogger(__name__) class JanusVideoRoomViewSet(viewsets.ViewSet): """ ViewSet для управления видеокомнатами через Janus Gateway. Параллельная реализация с IonSFUVideoRoomViewSet. """ permission_classes = [IsAuthenticated] lookup_field = 'room_id' @action(detail=False, methods=['post'], url_path='create-room') def create_room(self, request): """ Создать новую видеокомнату в Janus Gateway. POST /api/video/janus/create-room/ Body: { "lesson_id": 123, "is_recording": false, "max_participants": 6 } """ lesson_id = request.data.get('lesson_id') is_recording = request.data.get('is_recording', False) max_participants = request.data.get('max_participants', 6) if not lesson_id: return Response( {'error': 'lesson_id обязателен'}, status=status.HTTP_400_BAD_REQUEST ) try: from apps.schedule.models import Lesson lesson = Lesson.objects.select_related('mentor', 'client', 'client__user').get(id=lesson_id) except Lesson.DoesNotExist: return Response( {'error': 'Занятие не найдено'}, status=status.HTTP_404_NOT_FOUND ) # Проверяем права if request.user not in [lesson.mentor, lesson.client]: return Response( {'error': 'У вас нет доступа к этому занятию'}, status=status.HTTP_403_FORBIDDEN ) # Проверяем, есть ли уже комната if hasattr(lesson, 'video_room'): return Response( {'error': 'Видеокомната для этого занятия уже существует'}, status=status.HTTP_400_BAD_REQUEST ) try: # Создаем комнату в Django video_room = VideoRoom.objects.create( lesson=lesson, mentor=lesson.mentor, client=lesson.client, is_recording=is_recording, max_participants=max_participants, sfu_type='janus' # Указываем тип SFU ) # Создаем комнату в Janus Gateway with get_janus_client() as janus: # Используем числовой ID для Janus (преобразуем UUID в число) janus_room_id = int(str(video_room.room_id).replace('-', '')[:12], 16) % (2**31 - 1) # Генерируем уникальный secret для комнаты (или используем из настроек) from django.conf import settings room_secret = getattr(settings, 'JANUS_ROOM_SECRET', 'adminpwd') janus.create_room( room_id=janus_room_id, description=f"Занятие: {lesson.title}", publishers=max_participants, secret=room_secret ) # Сохраняем Janus room ID в router_id video_room.router_id = str(janus_room_id) video_room.save() logger.info(f"Janus room created: Django={video_room.room_id}, Janus={janus_room_id}") serializer = VideoRoomSerializer(video_room) return Response(serializer.data, status=status.HTTP_201_CREATED) except Exception as e: logger.error(f"Error creating Janus room: {e}") # Удаляем комнату из Django, если не удалось создать в Janus if video_room: video_room.delete() return Response( {'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @action(detail=True, methods=['delete'], url_path='destroy-room', lookup_field='room_id') def destroy_room(self, request, room_id=None): """ Удалить видеокомнату из Janus Gateway. DELETE /api/video/janus/{room_id}/destroy-room/ """ try: video_room = VideoRoom.objects.select_related('lesson', 'mentor', 'client', 'client__user').get(room_id=room_id, sfu_type='janus') except VideoRoom.DoesNotExist: return Response( {'error': 'Видеокомната не найдена'}, status=status.HTTP_404_NOT_FOUND ) # Проверяем права client_user = video_room.client.user if hasattr(video_room.client, 'user') else video_room.client if request.user not in [video_room.mentor, client_user]: return Response( {'error': 'У вас нет доступа к этой комнате'}, status=status.HTTP_403_FORBIDDEN ) try: # Удаляем комнату из Janus if video_room.router_id: from django.conf import settings room_secret = getattr(settings, 'JANUS_ROOM_SECRET', 'adminpwd') with get_janus_client() as janus: janus.destroy_room(int(video_room.router_id), secret=room_secret) # Удаляем из Django video_room.delete() return Response(status=status.HTTP_204_NO_CONTENT) except Exception as e: logger.error(f"Error destroying Janus room: {e}") return Response( {'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @action(detail=True, methods=['get'], url_path='join', lookup_field='room_id') def join(self, request, room_id=None): """ Получить информацию для присоединения к Janus комнате. GET /api/video/janus/{room_id}/join/ Returns: { "room_id": "uuid", "janus_room_id": 12345, "http_url": "http://localhost:8088/janus", "ws_url": "ws://localhost:8188", "ice_servers": [...], "participant_info": {...}, "room_info": {...} } """ try: video_room = VideoRoom.objects.select_related('lesson', 'mentor', 'client', 'client__user').get(room_id=room_id, sfu_type='janus') except VideoRoom.DoesNotExist: return Response( {'error': 'Видеокомната не найдена'}, status=status.HTTP_404_NOT_FOUND ) # Проверяем права client_user = video_room.client.user if hasattr(video_room.client, 'user') else video_room.client if request.user not in [video_room.mentor, client_user]: return Response( {'error': 'У вас нет доступа к этой комнате'}, status=status.HTTP_403_FORBIDDEN ) # Получаем или создаем участника participant, created = VideoParticipant.objects.get_or_create( room=video_room, user=request.user, defaults={'is_connected': False} ) # ICE серверы (STUN/TURN) ice_servers = [ {'urls': 'stun:stun.l.google.com:19302'}, {'urls': 'stun:stun1.l.google.com:19302'} ] # Janus room ID из router_id janus_room_id = int(video_room.router_id) if video_room.router_id else None return Response({ 'room_id': str(video_room.room_id), 'janus_room_id': janus_room_id, 'http_url': settings.JANUS_HTTP_URL, 'ws_url': settings.JANUS_WS_URL, 'ice_servers': ice_servers, 'participant_info': { 'user_id': request.user.id, 'username': request.user.get_full_name(), 'role': 'publisher', # В Janus все участники могут быть publishers }, 'room_info': VideoRoomSerializer(video_room).data, 'sfu_type': 'janus' }) @action(detail=False, methods=['get'], url_path='health') def health(self, request): """ Проверить доступность Janus Gateway. GET /api/video/janus/health/ """ try: import requests response = requests.get( f"{settings.JANUS_HTTP_URL.replace('/janus', '')}/janus/info", timeout=2 ) response.raise_for_status() data = response.json() return Response({ 'status': 'healthy', 'janus_info': data }) except Exception as e: logger.error(f"Janus health check failed: {e}") return Response( { 'status': 'unhealthy', 'error': str(e) }, status=status.HTTP_503_SERVICE_UNAVAILABLE )