classroom isActive Participant
This commit is contained in:
parent
6eea5f8880
commit
08f5d11f28
10 changed files with 61 additions and 19 deletions
|
|
@ -13,4 +13,5 @@ public class ClassroomParticipantDto
|
||||||
public bool IsVideoMuted { get; set; }
|
public bool IsVideoMuted { get; set; }
|
||||||
public bool IsHandRaised { get; set; }
|
public bool IsHandRaised { get; set; }
|
||||||
public DateTime JoinTime { get; set; }
|
public DateTime JoinTime { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,12 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
public async Task<ClassroomDto> JoinClassAsync(Guid id)
|
public async Task<ClassroomDto> JoinClassAsync(Guid id)
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(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)
|
if (classSession.ParticipantCount >= classSession.MaxParticipants)
|
||||||
{
|
{
|
||||||
|
|
@ -206,7 +212,11 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
id,
|
id,
|
||||||
CurrentUser.Id,
|
CurrentUser.Id,
|
||||||
CurrentUser.Name,
|
CurrentUser.Name,
|
||||||
false // isTeacher
|
false, // isTeacher
|
||||||
|
classroomSettings?.DefaultMicrophoneState == "muted",
|
||||||
|
classroomSettings?.DefaultCameraState == "off",
|
||||||
|
false, // HandRaised
|
||||||
|
true // isActive
|
||||||
);
|
);
|
||||||
|
|
||||||
await _participantRepository.InsertAsync(participant);
|
await _participantRepository.InsertAsync(participant);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public class ClassroomParticipant : FullAuditedEntity<Guid>
|
||||||
public bool IsAudioMuted { get; set; } = false;
|
public bool IsAudioMuted { get; set; } = false;
|
||||||
public bool IsVideoMuted { get; set; } = false;
|
public bool IsVideoMuted { get; set; } = false;
|
||||||
public bool IsHandRaised { get; set; } = false;
|
public bool IsHandRaised { get; set; } = false;
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
public DateTime JoinTime { get; set; }
|
public DateTime JoinTime { get; set; }
|
||||||
public string ConnectionId { get; set; }
|
public string ConnectionId { get; set; }
|
||||||
|
|
||||||
|
|
@ -30,7 +31,8 @@ public class ClassroomParticipant : FullAuditedEntity<Guid>
|
||||||
bool isTeacher,
|
bool isTeacher,
|
||||||
bool isAudioMuted,
|
bool isAudioMuted,
|
||||||
bool isVideoMuted,
|
bool isVideoMuted,
|
||||||
bool isHandRaised
|
bool isHandRaised,
|
||||||
|
bool isActive
|
||||||
) : base(id)
|
) : base(id)
|
||||||
{
|
{
|
||||||
SessionId = sessionId;
|
SessionId = sessionId;
|
||||||
|
|
@ -40,6 +42,7 @@ public class ClassroomParticipant : FullAuditedEntity<Guid>
|
||||||
IsAudioMuted = isAudioMuted;
|
IsAudioMuted = isAudioMuted;
|
||||||
IsVideoMuted = isVideoMuted;
|
IsVideoMuted = isVideoMuted;
|
||||||
IsHandRaised = isHandRaised;
|
IsHandRaised = isHandRaised;
|
||||||
|
IsActive = isActive;
|
||||||
JoinTime = DateTime.UtcNow;
|
JoinTime = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Kurs.Platform.Migrations
|
namespace Kurs.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20250829093104_Initial")]
|
[Migration("20250830114522_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -1816,6 +1816,9 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasColumnType("datetime2")
|
.HasColumnType("datetime2")
|
||||||
.HasColumnName("DeletionTime");
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<bool>("IsAudioMuted")
|
b.Property<bool>("IsAudioMuted")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
|
@ -1996,6 +1996,7 @@ namespace Kurs.Platform.Migrations
|
||||||
IsAudioMuted = table.Column<bool>(type: "bit", nullable: false),
|
IsAudioMuted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
IsVideoMuted = table.Column<bool>(type: "bit", nullable: false),
|
IsVideoMuted = table.Column<bool>(type: "bit", nullable: false),
|
||||||
IsHandRaised = 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),
|
JoinTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
ConnectionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
ConnectionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
|
@ -1813,6 +1813,9 @@ namespace Kurs.Platform.Migrations
|
||||||
.HasColumnType("datetime2")
|
.HasColumnType("datetime2")
|
||||||
.HasColumnName("DeletionTime");
|
.HasColumnName("DeletionTime");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<bool>("IsAudioMuted")
|
b.Property<bool>("IsAudioMuted")
|
||||||
.HasColumnType("bit");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ public class ClassroomHub : Hub
|
||||||
isTeacher,
|
isTeacher,
|
||||||
classroomSettings.DefaultMicrophoneState == "muted",
|
classroomSettings.DefaultMicrophoneState == "muted",
|
||||||
classroomSettings.DefaultCameraState == "off",
|
classroomSettings.DefaultCameraState == "off",
|
||||||
false
|
false,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
participant.UpdateConnectionId(Context.ConnectionId);
|
participant.UpdateConnectionId(Context.ConnectionId);
|
||||||
await _participantRepository.InsertAsync(participant, autoSave: true);
|
await _participantRepository.InsertAsync(participant, autoSave: true);
|
||||||
|
|
@ -96,14 +97,19 @@ public class ClassroomHub : Hub
|
||||||
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
||||||
|
|
||||||
// 🔑 Yeni katılana mevcut katılımcıları gönder
|
// 🔑 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
|
var others = existingParticipants
|
||||||
.Where(x => x.ConnectionId != Context.ConnectionId)
|
.Where(x => x.ConnectionId != Context.ConnectionId)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
UserId = x.UserId,
|
UserId = x.UserId,
|
||||||
UserName = x.UserName,
|
UserName = x.UserName,
|
||||||
IsTeacher = x.IsTeacher
|
IsTeacher = x.IsTeacher,
|
||||||
|
IsActive = x.IsActive // ✅ aktiflik bilgisini de gönder
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
@ -111,7 +117,7 @@ public class ClassroomHub : Hub
|
||||||
|
|
||||||
// 🔑 Grup üyelerine yeni katılanı öğretmen bilgisiyle bildir
|
// 🔑 Grup üyelerine yeni katılanı öğretmen bilgisiyle bildir
|
||||||
await Clients.Group(sessionId.ToString())
|
await Clients.Group(sessionId.ToString())
|
||||||
.SendAsync("ParticipantJoined", userId, userName, isTeacher);
|
.SendAsync("ParticipantJoined", userId, userName, isTeacher, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HubMethodName("LeaveClass")]
|
[HubMethodName("LeaveClass")]
|
||||||
|
|
@ -138,6 +144,17 @@ public class ClassroomHub : Hub
|
||||||
await Clients.Group(sessionId.ToString())
|
await Clients.Group(sessionId.ToString())
|
||||||
.SendAsync("AttendanceUpdated", attendance);
|
.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())
|
await Clients.Group(sessionId.ToString())
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ export interface ClassroomParticipantDto {
|
||||||
isAudioMuted?: boolean
|
isAudioMuted?: boolean
|
||||||
isVideoMuted?: boolean
|
isVideoMuted?: boolean
|
||||||
isHandRaised?: boolean
|
isHandRaised?: boolean
|
||||||
|
isActive?: boolean
|
||||||
stream?: MediaStream
|
stream?: MediaStream
|
||||||
screenStream?: MediaStream
|
screenStream?: MediaStream
|
||||||
isScreenSharing?: boolean
|
isScreenSharing?: boolean
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export class SignalRService {
|
||||||
private connection!: signalR.HubConnection
|
private connection!: signalR.HubConnection
|
||||||
private isConnected: boolean = false
|
private isConnected: boolean = false
|
||||||
private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void
|
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 onParticipantLeft?: (userId: string) => void
|
||||||
private onChatMessage?: (message: ClassroomChatDto) => void
|
private onChatMessage?: (message: ClassroomChatDto) => void
|
||||||
private onParticipantMuted?: (userId: string, isMuted: boolean) => void
|
private onParticipantMuted?: (userId: string, isMuted: boolean) => void
|
||||||
|
|
@ -39,8 +39,8 @@ export class SignalRService {
|
||||||
this.onAttendanceUpdate?.(record)
|
this.onAttendanceUpdate?.(record)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.on('ParticipantJoined', (userId: string, name: string, isTeacher: boolean) => {
|
this.connection.on('ParticipantJoined', (userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
|
||||||
this.onParticipantJoined?.(userId, name, isTeacher)
|
this.onParticipantJoined?.(userId, name, isTeacher, isActive)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.on('ParticipantLeft', (userId: string) => {
|
this.connection.on('ParticipantLeft', (userId: string) => {
|
||||||
|
|
@ -391,7 +391,7 @@ export class SignalRService {
|
||||||
this.onAttendanceUpdate = callback
|
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
|
this.onParticipantJoined = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -262,8 +262,9 @@ const RoomDetail: React.FC = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
signalRServiceRef.current.setParticipantJoinHandler(
|
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 (userId === user.id) return
|
||||||
|
if (!isActive) return // ❌ pasif kullanıcıyı ekleme
|
||||||
|
|
||||||
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
|
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
|
||||||
|
|
||||||
|
|
@ -276,6 +277,7 @@ const RoomDetail: React.FC = () => {
|
||||||
isTeacher,
|
isTeacher,
|
||||||
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
||||||
isVideoMuted: classSettings.defaultCameraState === 'off',
|
isVideoMuted: classSettings.defaultCameraState === 'off',
|
||||||
|
isActive: isActive, // ✅ state’de tut
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,8 +293,6 @@ const RoomDetail: React.FC = () => {
|
||||||
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
|
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
} else {
|
|
||||||
console.log('Teacher yok, offer başlatılmadı.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
|
|
@ -302,11 +302,14 @@ const RoomDetail: React.FC = () => {
|
||||||
|
|
||||||
// 🔑 ExistingParticipants handler
|
// 🔑 ExistingParticipants handler
|
||||||
signalRServiceRef.current.setExistingParticipantsHandler(
|
signalRServiceRef.current.setExistingParticipantsHandler(
|
||||||
async (existing: { userId: string; userName: string; isTeacher: boolean }[]) => {
|
async (
|
||||||
|
existing: { userId: string; userName: string; isTeacher: boolean; isActive: boolean }[],
|
||||||
|
) => {
|
||||||
setParticipants((prev) => {
|
setParticipants((prev) => {
|
||||||
let updated = [...prev]
|
let updated = [...prev]
|
||||||
|
|
||||||
for (const participant of existing) {
|
for (const participant of existing) {
|
||||||
|
if (!participant.isActive) continue // ❌ pasif kullanıcıyı alma
|
||||||
if (participant.userId === user.id) continue
|
if (participant.userId === user.id) continue
|
||||||
if (!updated.find((p) => p.id === participant.userId)) {
|
if (!updated.find((p) => p.id === participant.userId)) {
|
||||||
updated.push({
|
updated.push({
|
||||||
|
|
@ -315,6 +318,7 @@ const RoomDetail: React.FC = () => {
|
||||||
isTeacher: participant.isTeacher,
|
isTeacher: participant.isTeacher,
|
||||||
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
|
||||||
isVideoMuted: classSettings.defaultCameraState === 'off',
|
isVideoMuted: classSettings.defaultCameraState === 'off',
|
||||||
|
isActive: participant.isActive, // ✅ state’de tut
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -324,6 +328,7 @@ const RoomDetail: React.FC = () => {
|
||||||
if (teacherExists) {
|
if (teacherExists) {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
for (const participant of existing) {
|
for (const participant of existing) {
|
||||||
|
if (!participant.isActive) continue // ❌ pasifleri bağlama
|
||||||
if (participant.userId === user.id) continue
|
if (participant.userId === user.id) continue
|
||||||
if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) {
|
if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) {
|
||||||
await webRTCServiceRef.current?.createPeerConnection(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
|
return updated
|
||||||
|
|
@ -812,7 +815,7 @@ const RoomDetail: React.FC = () => {
|
||||||
{/* Video Grid */}
|
{/* Video Grid */}
|
||||||
<div className="flex-1 relative overflow-hidden min-h-0 h-full">
|
<div className="flex-1 relative overflow-hidden min-h-0 h-full">
|
||||||
<ParticipantGrid
|
<ParticipantGrid
|
||||||
participants={participants}
|
participants={participants.filter((p) => p.isActive)}
|
||||||
localStream={user.role === 'observer' ? undefined : localStream}
|
localStream={user.role === 'observer' ? undefined : localStream}
|
||||||
currentUserId={user.id}
|
currentUserId={user.id}
|
||||||
currentUserName={user.name}
|
currentUserName={user.name}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue