uchill/backend/apps/video/token_views.py

286 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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<token>[^/.]+)', 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
)