uchill/backend/apps/video/consumers.py

338 lines
13 KiB
Python
Raw 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.

"""
WebSocket consumers для видеоконференций.
Обработка WebRTC signaling через Django Channels.
"""
import json
import logging
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.utils import timezone
logger = logging.getLogger(__name__)
class VideoRoomConsumer(AsyncWebsocketConsumer):
"""
WebSocket consumer для видеокомнаты.
Обрабатывает WebRTC signaling между участниками.
"""
async def connect(self):
"""Подключение к комнате."""
self.room_id = self.scope['url_route']['kwargs']['room_id']
self.room_group_name = f'video_room_{self.room_id}'
self.user = self.scope['user']
# Проверка аутентификации
if not self.user.is_authenticated:
await self.close()
return
# Проверка доступа к комнате
has_access = await self.check_room_access()
if not has_access:
await self.close()
return
# Присоединяемся к группе комнаты
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
# Отправляем подтверждение подключения
await self.send(text_data=json.dumps({
'type': 'connection_established',
'message': 'Подключение установлено',
'user_id': self.user.id
}))
# Уведомляем других участников
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'user_joined',
'user_id': self.user.id,
'username': self.user.get_full_name()
}
)
# Сохраняем информацию об участнике
await self.save_participant()
logger.info(f'User {self.user.id} connected to room {self.room_id}')
async def disconnect(self, close_code):
"""Отключение от комнаты."""
if hasattr(self, 'room_group_name'):
# Уведомляем других участников об отключении
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'user_left',
'user_id': self.user.id,
'username': self.user.get_full_name()
}
)
# Покидаем группу
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Обновляем статус участника
await self.update_participant_disconnect()
logger.info(f'User {self.user.id} disconnected from room {self.room_id}')
async def receive(self, text_data):
"""Получение сообщения от клиента."""
try:
data = json.loads(text_data)
message_type = data.get('type')
# Обработка разных типов сообщений
if message_type == 'offer':
await self.handle_offer(data)
elif message_type == 'answer':
await self.handle_answer(data)
elif message_type == 'ice-candidate':
await self.handle_ice_candidate(data)
elif message_type == 'media-state':
await self.handle_media_state(data)
elif message_type == 'start-screen-share':
await self.handle_screen_share(data, True)
elif message_type == 'stop-screen-share':
await self.handle_screen_share(data, False)
else:
logger.warning(f'Unknown message type: {message_type}')
except json.JSONDecodeError:
logger.error('Invalid JSON received')
except Exception as e:
logger.error(f'Error in receive: {str(e)}')
async def handle_offer(self, data):
"""Обработка WebRTC offer."""
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'webrtc_offer',
'offer': data.get('offer'),
'sender_id': self.user.id
}
)
async def handle_answer(self, data):
"""Обработка WebRTC answer."""
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'webrtc_answer',
'answer': data.get('answer'),
'sender_id': self.user.id
}
)
async def handle_ice_candidate(self, data):
"""Обработка ICE candidate."""
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'ice_candidate',
'candidate': data.get('candidate'),
'sender_id': self.user.id
}
)
async def handle_media_state(self, data):
"""Обработка изменения состояния медиа (вкл/выкл камера/микрофон)."""
audio_enabled = data.get('audio_enabled')
video_enabled = data.get('video_enabled')
# Сохраняем состояние
await self.update_participant_media_state(audio_enabled, video_enabled)
# Уведомляем других участников
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'media_state_changed',
'user_id': self.user.id,
'audio_enabled': audio_enabled,
'video_enabled': video_enabled
}
)
async def handle_screen_share(self, data, is_sharing):
"""Обработка демонстрации экрана."""
await self.update_participant_screen_sharing(is_sharing)
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'screen_share_changed',
'user_id': self.user.id,
'is_sharing': is_sharing
}
)
# Обработчики событий от группы
async def user_joined(self, event):
"""Пользователь присоединился."""
if event['user_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'user_joined',
'user_id': event['user_id'],
'username': event['username']
}))
async def user_left(self, event):
"""Пользователь покинул комнату."""
if event['user_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'user_left',
'user_id': event['user_id'],
'username': event['username']
}))
async def webrtc_offer(self, event):
"""Пересылка WebRTC offer."""
if event['sender_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'offer',
'offer': event['offer'],
'sender_id': event['sender_id']
}))
async def webrtc_answer(self, event):
"""Пересылка WebRTC answer."""
if event['sender_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'answer',
'answer': event['answer'],
'sender_id': event['sender_id']
}))
async def ice_candidate(self, event):
"""Пересылка ICE candidate."""
if event['sender_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'ice-candidate',
'candidate': event['candidate'],
'sender_id': event['sender_id']
}))
async def media_state_changed(self, event):
"""Изменение состояния медиа."""
if event['user_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'media-state-changed',
'user_id': event['user_id'],
'audio_enabled': event['audio_enabled'],
'video_enabled': event['video_enabled']
}))
async def screen_share_changed(self, event):
"""Изменение демонстрации экрана."""
if event['user_id'] != self.user.id:
await self.send(text_data=json.dumps({
'type': 'screen-share-changed',
'user_id': event['user_id'],
'is_sharing': event['is_sharing']
}))
# Работа с базой данных
@database_sync_to_async
def check_room_access(self):
"""Проверка доступа к комнате."""
from .models import VideoRoom
try:
room = VideoRoom.objects.get(room_id=self.room_id)
# Проверяем что пользователь - участник
return self.user in [room.mentor, room.client]
except VideoRoom.DoesNotExist:
return False
@database_sync_to_async
def save_participant(self):
"""Сохранение информации об участнике."""
from .models import VideoRoom, VideoParticipant
try:
room = VideoRoom.objects.get(room_id=self.room_id)
participant, created = VideoParticipant.objects.get_or_create(
room=room,
user=self.user,
defaults={
'is_connected': True,
'connection_id': self.channel_name
}
)
if not created:
participant.is_connected = True
participant.reconnection_count += 1
participant.save()
# Отмечаем что участник подключился
room.mark_participant_joined(self.user)
# Если оба подключились и комната в ожидании, начинаем
if room.both_joined and room.status == 'waiting':
room.start()
except Exception as e:
logger.error(f'Error saving participant: {str(e)}')
@database_sync_to_async
def update_participant_disconnect(self):
"""Обновление при отключении участника."""
from .models import VideoRoom, VideoParticipant
try:
room = VideoRoom.objects.get(room_id=self.room_id)
participant = VideoParticipant.objects.get(room=room, user=self.user)
participant.disconnect()
except Exception as e:
logger.error(f'Error updating participant disconnect: {str(e)}')
@database_sync_to_async
def update_participant_media_state(self, audio_enabled, video_enabled):
"""Обновление состояния медиа участника."""
from .models import VideoRoom, VideoParticipant
try:
room = VideoRoom.objects.get(room_id=self.room_id)
participant = VideoParticipant.objects.get(room=room, user=self.user)
if audio_enabled is not None:
participant.is_audio_enabled = audio_enabled
if video_enabled is not None:
participant.is_video_enabled = video_enabled
participant.save()
except Exception as e:
logger.error(f'Error updating media state: {str(e)}')
@database_sync_to_async
def update_participant_screen_sharing(self, is_sharing):
"""Обновление демонстрации экрана."""
from .models import VideoRoom, VideoParticipant
try:
room = VideoRoom.objects.get(room_id=self.room_id)
participant = VideoParticipant.objects.get(room=room, user=self.user)
participant.is_screen_sharing = is_sharing
participant.save()
except Exception as e:
logger.error(f'Error updating screen sharing: {str(e)}')