classroom düzeltmeler
This commit is contained in:
parent
8acbd2e257
commit
dfd05cbf57
3 changed files with 77 additions and 59 deletions
|
|
@ -117,7 +117,6 @@ public class ClassroomHub : Hub
|
||||||
[HubMethodName("JoinClass")]
|
[HubMethodName("JoinClass")]
|
||||||
public async Task JoinClassAsync(Guid sessionId, Guid userId, string userName, bool isTeacher, bool isActive)
|
public async Task JoinClassAsync(Guid sessionId, Guid userId, string userName, bool isTeacher, bool isActive)
|
||||||
{
|
{
|
||||||
bool initialMuteState;
|
|
||||||
var classroom = await _classSessionRepository.GetAsync(sessionId);
|
var classroom = await _classSessionRepository.GetAsync(sessionId);
|
||||||
if (classroom == null)
|
if (classroom == null)
|
||||||
{
|
{
|
||||||
|
|
@ -129,7 +128,7 @@ public class ClassroomHub : Hub
|
||||||
? new ClassroomSettingsDto()
|
? new ClassroomSettingsDto()
|
||||||
: JsonSerializer.Deserialize<ClassroomSettingsDto>(classroom.SettingsJson);
|
: JsonSerializer.Deserialize<ClassroomSettingsDto>(classroom.SettingsJson);
|
||||||
|
|
||||||
initialMuteState = !isTeacher && classroomSettings.AutoMuteNewParticipants
|
bool initialMuteState = !isTeacher && classroomSettings.AutoMuteNewParticipants
|
||||||
? true
|
? true
|
||||||
: classroomSettings.DefaultMicrophoneState == "muted";
|
: classroomSettings.DefaultMicrophoneState == "muted";
|
||||||
|
|
||||||
|
|
@ -137,15 +136,14 @@ public class ClassroomHub : Hub
|
||||||
x => x.SessionId == sessionId && x.UserId == userId
|
x => x.SessionId == sessionId && x.UserId == userId
|
||||||
);
|
);
|
||||||
|
|
||||||
// 🚨 Kick edilmiş kullanıcı tekrar giriş yapamaz
|
// ❌ Kicklenmiş kullanıcı tekrar giremez
|
||||||
if (participant != null && participant.IsKicked)
|
if (participant != null && participant.IsKicked)
|
||||||
{
|
{
|
||||||
await Clients.Caller.SendAsync("Error", "You are not allowed to rejoin this class.");
|
await Clients.Caller.SendAsync("Error", "You are not allowed to rejoin this class.");
|
||||||
Context.Abort(); // bağlantıyı anında kapat
|
Context.Abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (participant == null)
|
if (participant == null)
|
||||||
{
|
{
|
||||||
participant = new ClassroomParticipant(
|
participant = new ClassroomParticipant(
|
||||||
|
|
@ -161,15 +159,20 @@ public class ClassroomHub : Hub
|
||||||
isActive
|
isActive
|
||||||
);
|
);
|
||||||
await _participantRepository.InsertAsync(participant, autoSave: true);
|
await _participantRepository.InsertAsync(participant, autoSave: true);
|
||||||
await UpdateParticipantConnectionAsync(participant, Context.ConnectionId, isActive);
|
|
||||||
await UpdateParticipantCountAsync(sessionId, classroom);
|
await UpdateParticipantCountAsync(sessionId, classroom);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
await UpdateParticipantConnectionAsync(participant, Context.ConnectionId, isActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
await UpdateParticipantConnectionAsync(participant, Context.ConnectionId, isActive);
|
||||||
|
|
||||||
|
// ✅ Attendance sadece yoksa aç
|
||||||
|
var existingOpenAttendance = await _attendanceRepository.FirstOrDefaultAsync(
|
||||||
|
x => x.SessionId == sessionId && x.StudentId == userId && x.LeaveTime == null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingOpenAttendance == null)
|
||||||
|
{
|
||||||
await CreateAttendanceAsync(sessionId, userId, userName);
|
await CreateAttendanceAsync(sessionId, userId, userName);
|
||||||
|
}
|
||||||
|
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
||||||
|
|
||||||
|
|
@ -189,9 +192,11 @@ public class ClassroomHub : Hub
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
await Clients.Caller.SendAsync("ExistingParticipants", others);
|
await Clients.Caller.SendAsync("ExistingParticipants", others);
|
||||||
|
|
||||||
await Clients.Group(sessionId.ToString())
|
await Clients.Group(sessionId.ToString())
|
||||||
.SendAsync("ParticipantJoined", userId, userName, isTeacher, isActive);
|
.SendAsync("ParticipantJoined", userId, userName, isTeacher, isActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HubMethodName("LeaveClass")]
|
[HubMethodName("LeaveClass")]
|
||||||
public async Task LeaveClassAsync(Guid sessionId)
|
public async Task LeaveClassAsync(Guid sessionId)
|
||||||
{
|
{
|
||||||
|
|
@ -372,20 +377,21 @@ public class ClassroomHub : Hub
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 1. Attendance kapat
|
// 1. Attendance kapat (sadece kicklenen için)
|
||||||
var attendances = await _attendanceRepository.GetListAsync(
|
var attendances = await _attendanceRepository.GetListAsync(
|
||||||
x => x.SessionId == sessionId && x.StudentId == participantId && x.LeaveTime == null
|
x => x.SessionId == sessionId &&
|
||||||
|
x.StudentId == participantId &&
|
||||||
|
x.LeaveTime == null
|
||||||
);
|
);
|
||||||
|
|
||||||
if (attendances != null)
|
|
||||||
{
|
|
||||||
foreach (var attendance in attendances)
|
foreach (var attendance in attendances)
|
||||||
{
|
{
|
||||||
await CloseAttendanceAsync(attendance);
|
await CloseAttendanceAsync(attendance);
|
||||||
|
|
||||||
|
// yalnızca diğer katılımcılar görebilsin
|
||||||
await Clients.Group(sessionId.ToString())
|
await Clients.Group(sessionId.ToString())
|
||||||
.SendAsync("AttendanceUpdated", attendance);
|
.SendAsync("AttendanceUpdated", attendance);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Participant bul
|
// 2. Participant bul
|
||||||
var participant = await _participantRepository.FirstOrDefaultAsync(
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
||||||
|
|
@ -394,22 +400,23 @@ public class ClassroomHub : Hub
|
||||||
|
|
||||||
if (participant == null)
|
if (participant == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("KickParticipant: Session {SessionId} için participant {ParticipantId} bulunamadı",
|
_logger.LogWarning(
|
||||||
|
"KickParticipant: Session {SessionId} için participant {ParticipantId} bulunamadı",
|
||||||
sessionId, participantId);
|
sessionId, participantId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectionId'yi kaydet (DB'ye null yazmadan önce)
|
// ConnectionId'yi cache et (null yazmadan önce)
|
||||||
var connectionId = participant.ConnectionId;
|
var connectionId = participant.ConnectionId;
|
||||||
|
|
||||||
// 3. DB'de kick flag setle
|
// 3. DB güncelle
|
||||||
participant.IsActive = false;
|
participant.IsActive = false;
|
||||||
participant.IsKicked = true;
|
participant.IsKicked = true;
|
||||||
participant.ConnectionId = null;
|
participant.ConnectionId = null;
|
||||||
|
|
||||||
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
||||||
|
|
||||||
// 4. Hedef kullanıcıya bildir
|
// 4. Hedef kullanıcıya bildir (ForceDisconnect)
|
||||||
if (!string.IsNullOrEmpty(connectionId))
|
if (!string.IsNullOrEmpty(connectionId))
|
||||||
{
|
{
|
||||||
await Clients.Client(connectionId)
|
await Clients.Client(connectionId)
|
||||||
|
|
@ -418,9 +425,13 @@ public class ClassroomHub : Hub
|
||||||
await Groups.RemoveFromGroupAsync(connectionId, sessionId.ToString());
|
await Groups.RemoveFromGroupAsync(connectionId, sessionId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Diğer katılımcılara bildir
|
// 5. Diğer katılımcılara duyur
|
||||||
await Clients.Group(sessionId.ToString())
|
await Clients.Group(sessionId.ToString())
|
||||||
.SendAsync("ParticipantLeft", new { UserId = participantId, SessionId = sessionId });
|
.SendAsync("ParticipantLeft", new
|
||||||
|
{
|
||||||
|
UserId = participantId,
|
||||||
|
SessionId = sessionId
|
||||||
|
});
|
||||||
|
|
||||||
// 6. Log
|
// 6. Log
|
||||||
_logger.LogInformation("👢 Participant {ParticipantId} kicked from session {SessionId}",
|
_logger.LogInformation("👢 Participant {ParticipantId} kicked from session {SessionId}",
|
||||||
|
|
@ -428,7 +439,8 @@ public class ClassroomHub : Hub
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "❌ KickParticipant hata verdi (Session={SessionId}, Participant={ParticipantId})",
|
_logger.LogError(ex,
|
||||||
|
"❌ KickParticipant hata verdi (Session={SessionId}, Participant={ParticipantId})",
|
||||||
sessionId, participantId);
|
sessionId, participantId);
|
||||||
|
|
||||||
await Clients.Caller.SendAsync("Error", "Kick işlemi başarısız oldu.");
|
await Clients.Caller.SendAsync("Error", "Kick işlemi başarısız oldu.");
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export class SignalRService {
|
||||||
.withUrl(`${import.meta.env.VITE_API_URL}/classroomhub`, {
|
.withUrl(`${import.meta.env.VITE_API_URL}/classroomhub`, {
|
||||||
accessTokenFactory: () => auth.session.token || '',
|
accessTokenFactory: () => auth.session.token || '',
|
||||||
})
|
})
|
||||||
.withAutomaticReconnect()
|
//.withAutomaticReconnect()
|
||||||
.configureLogging(signalR.LogLevel.Information)
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import {
|
import {
|
||||||
FaUsers,
|
|
||||||
FaComments,
|
FaComments,
|
||||||
FaUserPlus,
|
|
||||||
FaExpand,
|
FaExpand,
|
||||||
FaHandPaper,
|
FaHandPaper,
|
||||||
FaVolumeMute,
|
FaVolumeMute,
|
||||||
|
|
@ -19,7 +17,6 @@ import {
|
||||||
FaCompress,
|
FaCompress,
|
||||||
FaUserFriends,
|
FaUserFriends,
|
||||||
FaLayerGroup,
|
FaLayerGroup,
|
||||||
FaWrench,
|
|
||||||
FaFilePdf,
|
FaFilePdf,
|
||||||
FaFileWord,
|
FaFileWord,
|
||||||
FaFileImage,
|
FaFileImage,
|
||||||
|
|
@ -278,40 +275,45 @@ const RoomDetail: React.FC = () => {
|
||||||
await webRTCServiceRef.current?.addIceCandidate(fromUserId, candidate)
|
await webRTCServiceRef.current?.addIceCandidate(fromUserId, candidate)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 🔑 Yeni katılan birini gördüğünde
|
||||||
signalRServiceRef.current.setParticipantJoinHandler(
|
signalRServiceRef.current.setParticipantJoinHandler(
|
||||||
async (userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
|
async (remoteUserId: string, name: string, isTeacher: boolean, isActive: boolean) => {
|
||||||
if (userId === user.id) return
|
if (remoteUserId === user.id) return
|
||||||
if (!isActive) return
|
if (!isActive) return
|
||||||
|
|
||||||
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
|
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
|
||||||
|
|
||||||
|
// State’e ekle
|
||||||
setParticipants((prev) => {
|
setParticipants((prev) => {
|
||||||
const updated = [...prev]
|
if (prev.find((p) => p.id === remoteUserId)) return prev
|
||||||
if (!updated.find((p) => p.id === userId)) {
|
return [
|
||||||
updated.push({
|
...prev,
|
||||||
id: userId,
|
{
|
||||||
|
id: remoteUserId,
|
||||||
name,
|
name,
|
||||||
sessionId: classSession.id,
|
sessionId: classSession.id,
|
||||||
isTeacher,
|
isTeacher,
|
||||||
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
||||||
isVideoMuted: classSettings.defaultCameraState === 'off',
|
isVideoMuted: classSettings.defaultCameraState === 'off',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
})
|
},
|
||||||
}
|
]
|
||||||
return updated
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔑 Mesh: Herkes herkese offer gönderir
|
// PeerConnection hazırla
|
||||||
if (!webRTCServiceRef.current?.getPeerConnection(userId)) {
|
if (!webRTCServiceRef.current?.getPeerConnection(remoteUserId)) {
|
||||||
await webRTCServiceRef.current?.createPeerConnection(userId)
|
await webRTCServiceRef.current?.createPeerConnection(remoteUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const offer = await webRTCServiceRef.current!.createOffer(userId)
|
// 🔑 Çakışmayı önle: sadece id’si küçük olan offer başlatır
|
||||||
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
|
if (user.id < remoteUserId) {
|
||||||
|
const offer = await webRTCServiceRef.current!.createOffer(remoteUserId)
|
||||||
|
await signalRServiceRef.current?.sendOffer(classSession.id, remoteUserId, offer)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 🔑 ExistingParticipants handler
|
// 🔑 Odaya girdiğinde var olan katılımcılar
|
||||||
signalRServiceRef.current.setExistingParticipantsHandler(
|
signalRServiceRef.current.setExistingParticipantsHandler(
|
||||||
async (
|
async (
|
||||||
existing: { userId: string; userName: string; isTeacher: boolean; isActive: boolean }[],
|
existing: { userId: string; userName: string; isTeacher: boolean; isActive: boolean }[],
|
||||||
|
|
@ -320,10 +322,12 @@ const RoomDetail: React.FC = () => {
|
||||||
if (!participant.isActive) continue
|
if (!participant.isActive) continue
|
||||||
if (participant.userId === user.id) continue
|
if (participant.userId === user.id) continue
|
||||||
|
|
||||||
|
// State’e ekle
|
||||||
setParticipants((prev) => {
|
setParticipants((prev) => {
|
||||||
const updated = [...prev]
|
if (prev.find((p) => p.id === participant.userId)) return prev
|
||||||
if (!updated.find((p) => p.id === participant.userId)) {
|
return [
|
||||||
updated.push({
|
...prev,
|
||||||
|
{
|
||||||
id: participant.userId,
|
id: participant.userId,
|
||||||
name: participant.userName,
|
name: participant.userName,
|
||||||
sessionId: classSession.id,
|
sessionId: classSession.id,
|
||||||
|
|
@ -331,19 +335,21 @@ const RoomDetail: React.FC = () => {
|
||||||
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
||||||
isVideoMuted: classSettings.defaultCameraState === 'off',
|
isVideoMuted: classSettings.defaultCameraState === 'off',
|
||||||
isActive: true,
|
isActive: true,
|
||||||
})
|
},
|
||||||
}
|
]
|
||||||
return updated
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// PeerConnection hazırla
|
||||||
if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) {
|
if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) {
|
||||||
await webRTCServiceRef.current?.createPeerConnection(participant.userId)
|
await webRTCServiceRef.current?.createPeerConnection(participant.userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔑 Mesh: yeni gelen user da karşıya offer yollar
|
// 🔑 Çakışmayı önle
|
||||||
|
if (user.id < participant.userId) {
|
||||||
const offer = await webRTCServiceRef.current!.createOffer(participant.userId)
|
const offer = await webRTCServiceRef.current!.createOffer(participant.userId)
|
||||||
await signalRServiceRef.current?.sendOffer(classSession.id, participant.userId, offer)
|
await signalRServiceRef.current?.sendOffer(classSession.id, participant.userId, offer)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue