""" Views для работы с токенами доступа к видеокомнатам. """ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAuthenticated from django.shortcuts import get_object_or_404 from .models import VideoRoom from .serializers import VideoRoomSerializer from apps.users.utils import format_datetime_for_user import logging logger = logging.getLogger(__name__) class VideoRoomTokenViewSet(viewsets.ViewSet): """ ViewSet для работы с токенами доступа к видеокомнатам. """ @action(detail=False, methods=['get'], url_path='join/(?P[^/.]+)', permission_classes=[AllowAny]) def join_by_token(self, request, token=None): """ Получить информацию о комнате по токену доступа. GET /api/video/token/join/{token}/ Возвращает информацию для подключения к комнате. Доступно без авторизации - по токену. """ try: # Находим комнату по токену с оптимизацией запросов room = get_object_or_404( VideoRoom.objects.select_related('lesson', 'mentor', 'client', 'client__user'), access_token=token ) # Проверяем статус занятия if room.lesson.status == 'cancelled': return Response( {'error': 'Занятие отменено'}, status=status.HTTP_400_BAD_REQUEST ) # Проверяем, что занятие скоро начнется или уже идет from django.utils import timezone from datetime import timedelta now = timezone.now() lesson_start = room.lesson.start_time lesson_end = lesson_start + timedelta(minutes=room.lesson.duration) # Разрешаем подключиться за 15 минут до начала can_join_from = lesson_start - timedelta(minutes=15) if now < can_join_from: return Response( { 'error': 'Слишком рано для подключения', 'message': f'Занятие начнется {lesson_start.strftime("%d.%m.%Y в %H:%M")}. Подключиться можно за 15 минут до начала.' }, status=status.HTTP_400_BAD_REQUEST ) # Формируем ответ в зависимости от типа SFU if room.sfu_type == 'janus': return self._get_janus_join_info(room) else: return self._get_ion_sfu_join_info(room) except VideoRoom.DoesNotExist: return Response( {'error': 'Комната не найдена или токен недействителен'}, status=status.HTTP_404_NOT_FOUND ) except Exception as e: logger.error(f"Error in join_by_token: {e}") return Response( {'error': 'Произошла ошибка при подключении к комнате'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) def _get_janus_join_info(self, room: VideoRoom): """Получить информацию для подключения к Janus комнате""" from django.conf import settings # ICE серверы 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(room.router_id) if room.router_id else None # Информация об участниках mentor_name = f"{room.mentor.first_name} {room.mentor.last_name}".strip() or room.mentor.email if hasattr(room.lesson.client, 'user'): client_name = f"{room.lesson.client.user.first_name} {room.lesson.client.user.last_name}".strip() or room.lesson.client.user.email else: client_name = "Ученик" return Response({ 'success': True, 'room_id': str(room.room_id), 'janus_room_id': janus_room_id, 'sfu_type': 'janus', 'http_url': getattr(settings, 'JANUS_CLIENT_HTTP_URL', 'http://localhost:8088/janus'), 'ws_url': getattr(settings, 'JANUS_CLIENT_WS_URL', 'ws://localhost:8188'), 'ice_servers': ice_servers, 'lesson': { 'id': room.lesson.id, 'title': room.lesson.title, 'subject': room.lesson.subject, 'start_time': format_datetime_for_user(room.lesson.start_time, request.user.timezone) if room.lesson.start_time else None, 'duration': room.lesson.duration, }, 'participants': { 'mentor': { 'id': room.mentor.id, 'name': mentor_name, }, 'client': { 'name': client_name, } } }) def _get_ion_sfu_join_info(self, room: VideoRoom): """Получить информацию для подключения к ion-sfu комнате""" from django.conf import settings # ICE серверы ice_servers = [ {'urls': 'stun:stun.l.google.com:19302'}, {'urls': 'stun:stun1.l.google.com:19302'} ] # WebSocket URL для ion-sfu sfu_ws_url = getattr(settings, 'SFU_WS_URL', 'ws://localhost:7001') if 'sfu-server' in sfu_ws_url: sfu_ws_url = sfu_ws_url.replace('sfu-server', 'localhost') ws_url = f"{sfu_ws_url}/ws/{room.room_id}" # Информация об участниках mentor_name = f"{room.mentor.first_name} {room.mentor.last_name}".strip() or room.mentor.email if hasattr(room.lesson.client, 'user'): client_name = f"{room.lesson.client.user.first_name} {room.lesson.client.user.last_name}".strip() or room.lesson.client.user.email else: client_name = "Ученик" return Response({ 'success': True, 'room_id': str(room.room_id), 'sfu_type': 'ion-sfu', 'ws_url': ws_url, 'ice_servers': ice_servers, 'lesson': { 'id': room.lesson.id, 'title': room.lesson.title, 'subject': room.lesson.subject, 'start_time': format_datetime_for_user(room.lesson.start_time, request.user.timezone) if room.lesson.start_time else None, 'duration': room.lesson.duration, }, 'participants': { 'mentor': { 'id': room.mentor.id, 'name': mentor_name, }, 'client': { 'name': client_name, } } }) @action(detail=False, methods=['post'], url_path='create-for-lesson', permission_classes=[IsAuthenticated]) def create_for_lesson(self, request): """ Создать видеокомнату для занятия. POST /api/video/token/create-for-lesson/ Body: { "lesson_id": 123, "sfu_type": "janus" // или "ion-sfu" } """ lesson_id = request.data.get('lesson_id') sfu_type = request.data.get('sfu_type', 'janus') 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 != lesson.mentor: return Response( {'error': 'Только ментор может создать видеокомнату для занятия'}, status=status.HTTP_403_FORBIDDEN ) # Проверяем, есть ли уже комната if hasattr(lesson, 'video_room'): room = lesson.video_room return Response({ 'success': True, 'room_id': str(room.room_id), 'access_token': room.access_token, 'join_url': room.get_join_url(request), 'message': 'Комната уже существует' }) room = None try: # Создаем комнату # lesson.client - это Client объект, нужен User client_user = lesson.client.user if hasattr(lesson.client, 'user') else lesson.client room = VideoRoom.objects.create( lesson=lesson, mentor=lesson.mentor, client=client_user, sfu_type=sfu_type, max_participants=6 if lesson.group else 2 ) # Если используем Janus, создаем комнату на сервере if sfu_type == 'janus': from .janus_client import get_janus_client from django.conf import settings with get_janus_client() as janus: janus_room_id = int(str(room.room_id).replace('-', '')[:12], 16) % (2**31 - 1) room_secret = getattr(settings, 'JANUS_ROOM_SECRET', 'adminpwd') janus.create_room( room_id=janus_room_id, description=f"Занятие: {lesson.title}", publishers=room.max_participants, secret=room_secret ) room.router_id = str(janus_room_id) room.save() logger.info(f"Video room created for lesson {lesson_id}: {room.room_id}") return Response({ 'success': True, 'room_id': str(room.room_id), 'access_token': room.access_token, 'join_url': room.get_join_url(request), 'sfu_type': sfu_type }, status=status.HTTP_201_CREATED) except Exception as e: logger.error(f"Error creating video room: {e}") # Удаляем комнату из Django, если она была создана if room is not None: try: room.delete() except Exception as delete_error: logger.error(f"Error deleting room after failed creation: {delete_error}") return Response( {'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )