classroom isActive Participant

This commit is contained in:
Sedat Öztürk 2025-08-30 15:05:50 +03:00
parent 6eea5f8880
commit 08f5d11f28
10 changed files with 61 additions and 19 deletions

View file

@ -13,4 +13,5 @@ public class ClassroomParticipantDto
public bool IsVideoMuted { get; set; }
public bool IsHandRaised { get; set; }
public DateTime JoinTime { get; set; }
public bool IsActive { get; set; }
}

View file

@ -187,6 +187,12 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
public async Task<ClassroomDto> JoinClassAsync(Guid id)
{
var classSession = await _classSessionRepository.GetAsync(id);
if (classSession == null)
{
throw new InvalidOperationException("Class not found");
}
var classroomSettings = JsonSerializer.Deserialize<ClassroomSettingsDto>(classSession.SettingsJson);
if (classSession.ParticipantCount >= classSession.MaxParticipants)
{
@ -206,7 +212,11 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
id,
CurrentUser.Id,
CurrentUser.Name,
false // isTeacher
false, // isTeacher
classroomSettings?.DefaultMicrophoneState == "muted",
classroomSettings?.DefaultCameraState == "off",
false, // HandRaised
true // isActive
);
await _participantRepository.InsertAsync(participant);

View file

@ -12,6 +12,7 @@ public class ClassroomParticipant : FullAuditedEntity<Guid>
public bool IsAudioMuted { get; set; } = false;
public bool IsVideoMuted { get; set; } = false;
public bool IsHandRaised { get; set; } = false;
public bool IsActive { get; set; } = true;
public DateTime JoinTime { get; set; }
public string ConnectionId { get; set; }
@ -30,7 +31,8 @@ public class ClassroomParticipant : FullAuditedEntity<Guid>
bool isTeacher,
bool isAudioMuted,
bool isVideoMuted,
bool isHandRaised
bool isHandRaised,
bool isActive
) : base(id)
{
SessionId = sessionId;
@ -40,6 +42,7 @@ public class ClassroomParticipant : FullAuditedEntity<Guid>
IsAudioMuted = isAudioMuted;
IsVideoMuted = isVideoMuted;
IsHandRaised = isHandRaised;
IsActive = isActive;
JoinTime = DateTime.UtcNow;
}

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20250829093104_Initial")]
[Migration("20250830114522_Initial")]
partial class Initial
{
/// <inheritdoc />
@ -1816,6 +1816,9 @@ namespace Kurs.Platform.Migrations
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsAudioMuted")
.HasColumnType("bit");

View file

@ -1996,6 +1996,7 @@ namespace Kurs.Platform.Migrations
IsAudioMuted = table.Column<bool>(type: "bit", nullable: false),
IsVideoMuted = table.Column<bool>(type: "bit", nullable: false),
IsHandRaised = table.Column<bool>(type: "bit", nullable: false),
IsActive = table.Column<bool>(type: "bit", nullable: false),
JoinTime = table.Column<DateTime>(type: "datetime2", nullable: false),
ConnectionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),

View file

@ -1813,6 +1813,9 @@ namespace Kurs.Platform.Migrations
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool>("IsAudioMuted")
.HasColumnType("bit");

View file

@ -67,7 +67,8 @@ public class ClassroomHub : Hub
isTeacher,
classroomSettings.DefaultMicrophoneState == "muted",
classroomSettings.DefaultCameraState == "off",
false
false,
true
);
participant.UpdateConnectionId(Context.ConnectionId);
await _participantRepository.InsertAsync(participant, autoSave: true);
@ -96,14 +97,19 @@ public class ClassroomHub : Hub
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
// 🔑 Yeni katılana mevcut katılımcıları gönder
var existingParticipants = await _participantRepository.GetListAsync(x => x.SessionId == sessionId);
// 🔑 Yeni katılana mevcut aktif katılımcıları gönder
var existingParticipants = await _participantRepository.GetListAsync(
x => x.SessionId == sessionId && x.IsActive
);
var others = existingParticipants
.Where(x => x.ConnectionId != Context.ConnectionId)
.Select(x => new
{
UserId = x.UserId,
UserName = x.UserName,
IsTeacher = x.IsTeacher
IsTeacher = x.IsTeacher,
IsActive = x.IsActive // ✅ aktiflik bilgisini de gönder
})
.ToList();
@ -111,7 +117,7 @@ public class ClassroomHub : Hub
// 🔑 Grup üyelerine yeni katılanı öğretmen bilgisiyle bildir
await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantJoined", userId, userName, isTeacher);
.SendAsync("ParticipantJoined", userId, userName, isTeacher, true);
}
[HubMethodName("LeaveClass")]
@ -138,6 +144,17 @@ public class ClassroomHub : Hub
await Clients.Group(sessionId.ToString())
.SendAsync("AttendanceUpdated", attendance);
}
//Kullanıcıyı Pasife aldım.
var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == userId
);
if (participant == null)
{
participant.IsActive = false;
await _participantRepository.UpdateAsync(participant, autoSave: true);
}
}
await Clients.Group(sessionId.ToString())

View file

@ -57,6 +57,7 @@ export interface ClassroomParticipantDto {
isAudioMuted?: boolean
isVideoMuted?: boolean
isHandRaised?: boolean
isActive?: boolean
stream?: MediaStream
screenStream?: MediaStream
isScreenSharing?: boolean

View file

@ -6,7 +6,7 @@ export class SignalRService {
private connection!: signalR.HubConnection
private isConnected: boolean = false
private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void
private onParticipantJoined?: (userId: string, name: string, isTeacher: boolean) => void
private onParticipantJoined?: (userId: string, name: string, isTeacher: boolean, isActive: boolean) => void
private onParticipantLeft?: (userId: string) => void
private onChatMessage?: (message: ClassroomChatDto) => void
private onParticipantMuted?: (userId: string, isMuted: boolean) => void
@ -39,8 +39,8 @@ export class SignalRService {
this.onAttendanceUpdate?.(record)
})
this.connection.on('ParticipantJoined', (userId: string, name: string, isTeacher: boolean) => {
this.onParticipantJoined?.(userId, name, isTeacher)
this.connection.on('ParticipantJoined', (userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
this.onParticipantJoined?.(userId, name, isTeacher, isActive)
})
this.connection.on('ParticipantLeft', (userId: string) => {
@ -391,7 +391,7 @@ export class SignalRService {
this.onAttendanceUpdate = callback
}
setParticipantJoinHandler(callback: (userId: string, name: string, isTeacher: boolean) => void) {
setParticipantJoinHandler(callback: (userId: string, name: string, isTeacher: boolean, isActive: boolean) => void) {
this.onParticipantJoined = callback
}

View file

@ -262,8 +262,9 @@ const RoomDetail: React.FC = () => {
})
signalRServiceRef.current.setParticipantJoinHandler(
async (userId: string, name: string, isTeacher: boolean) => {
async (userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
if (userId === user.id) return
if (!isActive) return // ❌ pasif kullanıcıyı ekleme
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
@ -276,6 +277,7 @@ const RoomDetail: React.FC = () => {
isTeacher,
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
isVideoMuted: classSettings.defaultCameraState === 'off',
isActive: isActive, // ✅ statede tut
})
}
@ -291,8 +293,6 @@ const RoomDetail: React.FC = () => {
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
}
})()
} else {
console.log('Teacher yok, offer başlatılmadı.')
}
return updated
@ -302,11 +302,14 @@ const RoomDetail: React.FC = () => {
// 🔑 ExistingParticipants handler
signalRServiceRef.current.setExistingParticipantsHandler(
async (existing: { userId: string; userName: string; isTeacher: boolean }[]) => {
async (
existing: { userId: string; userName: string; isTeacher: boolean; isActive: boolean }[],
) => {
setParticipants((prev) => {
let updated = [...prev]
for (const participant of existing) {
if (!participant.isActive) continue // ❌ pasif kullanıcıyı alma
if (participant.userId === user.id) continue
if (!updated.find((p) => p.id === participant.userId)) {
updated.push({
@ -315,6 +318,7 @@ const RoomDetail: React.FC = () => {
isTeacher: participant.isTeacher,
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
isVideoMuted: classSettings.defaultCameraState === 'off',
isActive: participant.isActive, // ✅ statede tut
})
}
}
@ -324,6 +328,7 @@ const RoomDetail: React.FC = () => {
if (teacherExists) {
;(async () => {
for (const participant of existing) {
if (!participant.isActive) continue // ❌ pasifleri bağlama
if (participant.userId === user.id) continue
if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) {
await webRTCServiceRef.current?.createPeerConnection(participant.userId)
@ -338,8 +343,6 @@ const RoomDetail: React.FC = () => {
}
}
})()
} else {
console.log('Teacher yok, offer başlatılmadı (existing).')
}
return updated
@ -812,7 +815,7 @@ const RoomDetail: React.FC = () => {
{/* Video Grid */}
<div className="flex-1 relative overflow-hidden min-h-0 h-full">
<ParticipantGrid
participants={participants}
participants={participants.filter((p) => p.isActive)}
localStream={user.role === 'observer' ? undefined : localStream}
currentUserId={user.id}
currentUserName={user.name}