Uyarılar değiştirildi kullanıcıya gösterildi. toast
This commit is contained in:
parent
60ced627b4
commit
9db84f137e
6 changed files with 258 additions and 226 deletions
|
|
@ -17712,7 +17712,7 @@
|
||||||
"isActive": false,
|
"isActive": false,
|
||||||
"isScheduled": true,
|
"isScheduled": true,
|
||||||
"participantCount": 0,
|
"participantCount": 0,
|
||||||
"settingsJson": null
|
"settingsJson": "{\"AllowHandRaise\":true,\"AllowStudentChat\":true,\"AllowPrivateMessages\":true,\"AllowStudentScreenShare\":true,\"DefaultMicrophoneState\":\"muted\",\"DefaultCameraState\":\"off\",\"DefaultLayout\":\"grid\",\"AutoMuteNewParticipants\":true}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fizik 201 - Kuantum Mekaniği",
|
"name": "Fizik 201 - Kuantum Mekaniği",
|
||||||
|
|
@ -17729,7 +17729,7 @@
|
||||||
"isActive": false,
|
"isActive": false,
|
||||||
"isScheduled": true,
|
"isScheduled": true,
|
||||||
"participantCount": 0,
|
"participantCount": 0,
|
||||||
"settingsJson": null
|
"settingsJson": "{\"AllowHandRaise\":true,\"AllowStudentChat\":true,\"AllowPrivateMessages\":true,\"AllowStudentScreenShare\":true,\"DefaultMicrophoneState\":\"muted\",\"DefaultCameraState\":\"off\",\"DefaultLayout\":\"grid\",\"AutoMuteNewParticipants\":true}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Kimya 301 - Organik Kimya",
|
"name": "Kimya 301 - Organik Kimya",
|
||||||
|
|
@ -17746,7 +17746,7 @@
|
||||||
"isActive": false,
|
"isActive": false,
|
||||||
"isScheduled": true,
|
"isScheduled": true,
|
||||||
"participantCount": 0,
|
"participantCount": 0,
|
||||||
"settingsJson": null
|
"settingsJson": "{\"AllowHandRaise\":true,\"AllowStudentChat\":true,\"AllowPrivateMessages\":true,\"AllowStudentScreenShare\":true,\"DefaultMicrophoneState\":\"muted\",\"DefaultCameraState\":\"off\",\"DefaultLayout\":\"grid\",\"AutoMuteNewParticipants\":true}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,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)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("🔵 JoinClass çağrıldı: User={UserId}, Session={SessionId}, IsTeacher={IsTeacher}", userId, sessionId, isTeacher);
|
|
||||||
|
|
||||||
var classroom = await _classSessionRepository.GetAsync(sessionId);
|
var classroom = await _classSessionRepository.GetAsync(sessionId);
|
||||||
if (classroom == null)
|
if (classroom == null)
|
||||||
{
|
{
|
||||||
|
|
@ -235,9 +233,6 @@ public class ClassroomHub : Hub
|
||||||
|
|
||||||
await Clients.Group(sessionId.ToString())
|
await Clients.Group(sessionId.ToString())
|
||||||
.SendAsync("ParticipantLeft", new { UserId = userId.Value, SessionId = sessionId });
|
.SendAsync("ParticipantLeft", new { UserId = userId.Value, SessionId = sessionId });
|
||||||
|
|
||||||
|
|
||||||
_logger.LogInformation("User {UserId} left class {SessionId}", userId, sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HubMethodName("MuteParticipant")]
|
[HubMethodName("MuteParticipant")]
|
||||||
|
|
@ -412,17 +407,13 @@ public class ClassroomHub : Hub
|
||||||
x => x.SessionId == sessionId && x.UserId == participantId
|
x => x.SessionId == sessionId && x.UserId == participantId
|
||||||
);
|
);
|
||||||
|
|
||||||
_logger.LogInformation("👢 KickParticipant çağrıldı: Session={SessionId}, Target={ParticipantId}", sessionId, participantId);
|
|
||||||
|
|
||||||
if (participant == null)
|
if (participant == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("⚠️ KickParticipant: participant bulunamadı (Session={SessionId}, Target={ParticipantId})", sessionId, participantId);
|
_logger.LogWarning($"⚠️ KickParticipant: participant bulunamadı Session={sessionId}, Target={participantId})");
|
||||||
|
await Clients.Caller.SendAsync("Warning", "Participant bulunamadı");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("✅ Kick öncesi durum: IsActive={IsActive}, IsKicked={IsKicked}, ConnId={ConnId}", participant.IsActive, participant.IsKicked, participant.ConnectionId);
|
|
||||||
|
|
||||||
|
|
||||||
// ConnectionId'yi cache et (null yazmadan önce)
|
// ConnectionId'yi cache et (null yazmadan önce)
|
||||||
var connectionId = participant.ConnectionId;
|
var connectionId = participant.ConnectionId;
|
||||||
|
|
||||||
|
|
@ -454,15 +445,12 @@ public class ClassroomHub : Hub
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6. Log
|
// 6. Log
|
||||||
_logger.LogInformation("👢 Participant {ParticipantId} kicked from session {SessionId}",
|
_logger.LogInformation($"👢 Participant {participantId} kicked from session {sessionId}");
|
||||||
participantId, sessionId);
|
await Clients.Caller.SendAsync("Info", "Kick işlemi başarısız oldu.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex,
|
_logger.LogError(ex, $"❌ KickParticipant hata verdi (Session={sessionId}, Participant={participantId})");
|
||||||
"❌ KickParticipant hata verdi (Session={SessionId}, Participant={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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -505,7 +493,6 @@ public class ClassroomHub : Hub
|
||||||
[HubMethodName("SendOffer")]
|
[HubMethodName("SendOffer")]
|
||||||
public async Task SendOfferAsync(Guid sessionId, Guid targetUserId, object offer)
|
public async Task SendOfferAsync(Guid sessionId, Guid targetUserId, object offer)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("➡️ SendOffer to {TargetUserId}, from {CurrentUser}", targetUserId, _currentUser.Id);
|
|
||||||
await Clients.User(targetUserId.ToString())
|
await Clients.User(targetUserId.ToString())
|
||||||
.SendAsync("ReceiveOffer", _currentUser.Id?.ToString(), offer);
|
.SendAsync("ReceiveOffer", _currentUser.Id?.ToString(), offer);
|
||||||
}
|
}
|
||||||
|
|
@ -532,13 +519,11 @@ public class ClassroomHub : Hub
|
||||||
var userId = _currentUser.Id;
|
var userId = _currentUser.Id;
|
||||||
if (!userId.HasValue)
|
if (!userId.HasValue)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("OnDisconnectedAsync çağrıldı fakat UserId bulunamadı. ConnId={ConnectionId}", Context.ConnectionId);
|
_logger.LogWarning($"OnDisconnectedAsync çağrıldı fakat UserId bulunamadı. ConnId={Context.ConnectionId}");
|
||||||
|
await Clients.Caller.SendAsync("Warning", "User bulunamadı");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("🔴 OnDisconnectedAsync: User={UserId}, ConnId={ConnId}, Exception={Exception}",
|
|
||||||
userId, Context.ConnectionId, exception?.Message);
|
|
||||||
|
|
||||||
// 🔑 Yeni scope aç (her disconnect için ayrı UoW)
|
// 🔑 Yeni scope aç (her disconnect için ayrı UoW)
|
||||||
using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);
|
using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);
|
||||||
var participants = await _participantRepository
|
var participants = await _participantRepository
|
||||||
|
|
@ -546,15 +531,15 @@ public class ClassroomHub : Hub
|
||||||
|
|
||||||
if (!participants.Any())
|
if (!participants.Any())
|
||||||
{
|
{
|
||||||
_logger.LogInformation("OnDisconnectedAsync: User {UserId} için aktif participant bulunamadı. ConnId={ConnectionId}", userId, Context.ConnectionId);
|
_logger.LogInformation($"OnDisconnectedAsync: User {userId} için aktif participant bulunamadı. ConnId={Context.ConnectionId}");
|
||||||
|
await Clients.Caller.SendAsync("Warning", "Aktif participant bulunamadı");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var participant in participants)
|
foreach (var participant in participants)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("OnDisconnectedAsync: User {UserId}, Session {SessionId} bağlantısı koptu.",
|
_logger.LogInformation($"OnDisconnectedAsync: User {userId}, Session {participant.SessionId} bağlantısı koptu.");
|
||||||
userId, participant.SessionId);
|
await Clients.Caller.SendAsync("Info", $"Bağlantı koptu: User {userId}");
|
||||||
|
|
||||||
// 🔑 Attendance güncelle
|
|
||||||
var attendances = await _attendanceRepository.GetListAsync(
|
var attendances = await _attendanceRepository.GetListAsync(
|
||||||
x => x.SessionId == participant.SessionId &&
|
x => x.SessionId == participant.SessionId &&
|
||||||
x.StudentId == userId.Value &&
|
x.StudentId == userId.Value &&
|
||||||
|
|
@ -606,10 +591,12 @@ public class ClassroomHub : Hub
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted). ConnId={ConnectionId}", Context.ConnectionId);
|
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted). ConnId={ConnectionId}", Context.ConnectionId);
|
||||||
|
await Clients.Caller.SendAsync("Error", "OnDisconnectedAsync iptal edildi");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "OnDisconnectedAsync hata. ConnId={ConnectionId}", Context.ConnectionId);
|
_logger.LogError(ex, "OnDisconnectedAsync hata. ConnId={ConnectionId}", Context.ConnectionId);
|
||||||
|
await Clients.Caller.SendAsync("Error", "OnDisconnectedAsync hata oluştu");
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.OnDisconnectedAsync(exception);
|
await base.OnDisconnectedAsync(exception);
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="p-3 sm:p-4 border-b border-gray-200 flex justify-between items-center">
|
<div className="p-3 sm:p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
<h3 className="text-base sm:text-lg font-semibold">Sohbet</h3>
|
<h3 className="text-base sm:text-lg font-semibold">Sohbet</h3>
|
||||||
<button onClick={onClose} className="p-1 hover:bg-gray-100 rounded lg:hidden">
|
<button onClick={onClose}>
|
||||||
<FaTimes className="text-gray-500" size={16} />
|
<FaTimes className="text-gray-500" size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { toast } from '@/components/ui'
|
|
||||||
import {
|
import {
|
||||||
ClassroomAttendanceDto,
|
ClassroomAttendanceDto,
|
||||||
ClassroomChatDto,
|
ClassroomChatDto,
|
||||||
HandRaiseDto,
|
HandRaiseDto,
|
||||||
MessageType,
|
|
||||||
} from '@/proxy/classroom/models'
|
} from '@/proxy/classroom/models'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { store } from '@/store/store'
|
import { store } from '@/store/store'
|
||||||
import * as signalR from '@microsoft/signalr'
|
import * as signalR from '@microsoft/signalr'
|
||||||
|
import { toast } from '@/components/ui'
|
||||||
import Notification from '@/components/ui/Notification'
|
import Notification from '@/components/ui/Notification'
|
||||||
|
|
||||||
export class SignalRService {
|
export class SignalRService {
|
||||||
|
|
@ -36,13 +35,10 @@ export class SignalRService {
|
||||||
constructor() {
|
constructor() {
|
||||||
const { auth } = store.getState()
|
const { auth } = store.getState()
|
||||||
|
|
||||||
// Only initialize connection if not in demo mode
|
|
||||||
// In production, replace with your actual SignalR hub URL
|
|
||||||
this.connection = new signalR.HubConnectionBuilder()
|
this.connection = new signalR.HubConnectionBuilder()
|
||||||
.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()
|
|
||||||
.configureLogging(signalR.LogLevel.Information)
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
@ -76,12 +72,10 @@ export class SignalRService {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.on('HandRaiseReceived', (payload: any) => {
|
this.connection.on('HandRaiseReceived', (payload: any) => {
|
||||||
// payload = { handRaiseId, studentId, studentName, ... }
|
|
||||||
this.onHandRaiseReceived?.(payload.studentId)
|
this.onHandRaiseReceived?.(payload.studentId)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.on('HandRaiseDismissed', (payload: any) => {
|
this.connection.on('HandRaiseDismissed', (payload: any) => {
|
||||||
// payload = { handRaiseId, studentId }
|
|
||||||
this.onHandRaiseDismissed?.(payload.studentId)
|
this.onHandRaiseDismissed?.(payload.studentId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -102,33 +96,25 @@ export class SignalRService {
|
||||||
|
|
||||||
this.connection.onreconnected(async () => {
|
this.connection.onreconnected(async () => {
|
||||||
this.isConnected = true
|
this.isConnected = true
|
||||||
console.log("🔄 SignalR reconnected. currentSessionId=", this.currentSessionId)
|
|
||||||
|
|
||||||
toast.push(<Notification title="🔄 Bağlantı tekrar kuruldu" type="success" />, {
|
toast.push(<Notification title="🔄 Bağlantı tekrar kuruldu" type="success" />, {
|
||||||
placement: 'top-center',
|
placement: 'top-center',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Eğer sınıftayken bağlantı koptuysa → tekrar join et
|
|
||||||
if (this.currentSessionId && store.getState().auth.user) {
|
if (this.currentSessionId && store.getState().auth.user) {
|
||||||
const u = store.getState().auth.user
|
const u = store.getState().auth.user
|
||||||
await this.joinClass(this.currentSessionId, u.id, u.name, u.role === 'teacher', true)
|
await this.joinClass(this.currentSessionId, u.id, u.name, u.role === 'teacher', true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.onclose(async (err) => {
|
this.connection.onclose(async () => {
|
||||||
console.warn("🔥 onclose triggered", { isKicked: this.isKicked, error: err })
|
|
||||||
|
|
||||||
if (this.isKicked) {
|
if (this.isKicked) {
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="⚠️ Bağlantı koptu, yeniden bağlanılıyor..." type="warning" />,
|
<Notification title="⚠️ Bağlantı koptu, yeniden bağlanılıyor..." type="warning" />,
|
||||||
{
|
{ placement: 'top-center' },
|
||||||
placement: 'top-center',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
this.isConnected = false
|
this.isConnected = false
|
||||||
this.currentSessionId = undefined
|
this.currentSessionId = undefined
|
||||||
return // ❗ Kick durumunda kesinlikle LeaveClass çağırma
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isConnected = false
|
this.isConnected = false
|
||||||
|
|
@ -136,46 +122,55 @@ export class SignalRService {
|
||||||
if (this.currentSessionId) {
|
if (this.currentSessionId) {
|
||||||
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
console.warn('LeaveClass could not be sent, connection was already closed')
|
|
||||||
} finally {
|
} finally {
|
||||||
this.currentSessionId = undefined
|
this.currentSessionId = undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.on('Error', (message: string) => {
|
this.connection.on('Error', (message: string) => {
|
||||||
console.error('Hub error:', message)
|
|
||||||
toast.push(<Notification title={`❌ Hata: ${message}`} type="danger" />, {
|
toast.push(<Notification title={`❌ Hata: ${message}`} type="danger" />, {
|
||||||
placement: 'top-center',
|
placement: 'top-center',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.onreconnecting((err) => {
|
this.connection.on('Warning', (message: string) => {
|
||||||
|
toast.push(<Notification title={`⚠️ Uyarı: ${message}`} type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.connection.on('Info', (message: string) => {
|
||||||
|
toast.push(<Notification title={`ℹ️ Bilgi: ${message}`} type="info" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.connection.onreconnecting(() => {
|
||||||
if (this.isKicked) {
|
if (this.isKicked) {
|
||||||
console.warn('Reconnect blocked because user was kicked')
|
toast.push(
|
||||||
|
<Notification
|
||||||
|
title="❌ Sınıftan çıkarıldığınız için yeniden bağlanma engellendi"
|
||||||
|
type="danger"
|
||||||
|
/>,
|
||||||
|
)
|
||||||
this.connection.stop()
|
this.connection.stop()
|
||||||
throw new Error('Reconnect blocked after kick')
|
throw new Error('Reconnect blocked after kick')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.connection.on('ForceDisconnect', async (message: string) => {
|
this.connection.on('ForceDisconnect', async (message: string) => {
|
||||||
console.warn("🚨 ForceDisconnect event alındı", message)
|
|
||||||
|
|
||||||
this.isKicked = true
|
this.isKicked = true
|
||||||
toast.push(<Notification title={`❌ Sınıftan çıkarıldınız: ${message}`} type="danger" />, {
|
toast.push(<Notification title={`❌ Sınıftan çıkarıldınız: ${message}`} type="danger" />, {
|
||||||
placement: 'top-center',
|
placement: 'top-center',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.onForceCleanup) {
|
if (this.onForceCleanup) {
|
||||||
console.warn('⚡ ForceCleanup callback çağrılıyor')
|
|
||||||
this.onForceCleanup()
|
this.onForceCleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.stop()
|
await this.connection.stop()
|
||||||
} catch (e) {
|
} catch {}
|
||||||
console.warn('connection.stop hata:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isConnected = false
|
this.isConnected = false
|
||||||
|
|
||||||
|
|
@ -193,8 +188,6 @@ export class SignalRService {
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('🔌 SignalR start() çağrıldı')
|
|
||||||
|
|
||||||
const startPromise = this.connection.start()
|
const startPromise = this.connection.start()
|
||||||
const timeout = new Promise((_, reject) =>
|
const timeout = new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error('Bağlantı zaman aşımına uğradı')), 10000),
|
setTimeout(() => reject(new Error('Bağlantı zaman aşımına uğradı')), 10000),
|
||||||
|
|
@ -205,10 +198,13 @@ export class SignalRService {
|
||||||
toast.push(<Notification title="✅ Bağlantı kuruldu" type="success" />, {
|
toast.push(<Notification title="✅ Bağlantı kuruldu" type="success" />, {
|
||||||
placement: 'top-center',
|
placement: 'top-center',
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error starting SignalR connection:', error)
|
toast.push(
|
||||||
alert(
|
<Notification
|
||||||
'⚠️ Sunucuya bağlanılamadı. Lütfen sayfayı yenileyin veya internet bağlantınızı kontrol edin.',
|
title="⚠️ Sunucuya bağlanılamadı. Lütfen sayfayı yenileyin veya internet bağlantınızı kontrol edin."
|
||||||
|
type="danger"
|
||||||
|
/>,
|
||||||
|
{ placement: 'top-center' },
|
||||||
)
|
)
|
||||||
this.isConnected = false
|
this.isConnected = false
|
||||||
}
|
}
|
||||||
|
|
@ -222,18 +218,22 @@ export class SignalRService {
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Error starting SignalR connection join class for', userName)
|
toast.push(
|
||||||
|
<Notification
|
||||||
|
title="⚠️ Bağlantı yok. Sınıfa katılmadan önce bağlantıyı kontrol edin."
|
||||||
|
type="warning"
|
||||||
|
/>,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log(`📡 joinClass: sessionId=${sessionId}, userId=${userId}, isTeacher=${isTeacher}`)
|
|
||||||
|
|
||||||
//Global değişkene yazılıyor.
|
|
||||||
this.currentSessionId = sessionId
|
this.currentSessionId = sessionId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive)
|
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error joining class:', error)
|
toast.push(<Notification title="❌ Sınıfa katılamadı" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,21 +241,17 @@ export class SignalRService {
|
||||||
const { auth } = store.getState()
|
const { auth } = store.getState()
|
||||||
|
|
||||||
if (!this.isConnected) {
|
if (!this.isConnected) {
|
||||||
console.log('Error starting SignalR connection simulating leave class for user', auth.user.id)
|
|
||||||
// Simulate successful leave in demo mode
|
|
||||||
setTimeout(() => {
|
|
||||||
this.onParticipantLeft?.({ userId: auth.user.id, sessionId })
|
this.onParticipantLeft?.({ userId: auth.user.id, sessionId })
|
||||||
}, 100)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('LeaveClass', sessionId)
|
await this.connection.invoke('LeaveClass', sessionId)
|
||||||
|
|
||||||
//Global değişkene null atanıyor.
|
|
||||||
this.currentSessionId = undefined
|
this.currentSessionId = undefined
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error leaving class:', error)
|
toast.push(<Notification title="⚠️ Çıkış başarısız" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,8 +289,10 @@ export class SignalRService {
|
||||||
isTeacher,
|
isTeacher,
|
||||||
'public',
|
'public',
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error sending chat message:', error)
|
toast.push(<Notification title="❌ Mesaj gönderilemedi" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,8 +336,10 @@ export class SignalRService {
|
||||||
isTeacher,
|
isTeacher,
|
||||||
'private',
|
'private',
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error sending private message:', error)
|
toast.push(<Notification title="❌ Özel mesaj gönderilemedi" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,8 +376,10 @@ export class SignalRService {
|
||||||
message,
|
message,
|
||||||
isTeacher,
|
isTeacher,
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error sending chat message:', error)
|
toast.push(<Notification title="❌ Duyuru gönderilemedi" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,20 +398,15 @@ export class SignalRService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
|
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error muting participant:', error)
|
toast.push(<Notification title="⚠️ Katılımcı susturulamadı" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
|
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
|
||||||
if (!this.isConnected) {
|
if (!this.isConnected) {
|
||||||
const handRaise: HandRaiseDto = {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
studentId,
|
|
||||||
studentName,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
isActive: true,
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onHandRaiseReceived?.(studentId)
|
this.onHandRaiseReceived?.(studentId)
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
@ -418,8 +415,10 @@ export class SignalRService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('RaiseHand', sessionId, studentId, studentName)
|
await this.connection.invoke('RaiseHand', sessionId, studentId, studentName)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error raising hand:', error)
|
toast.push(<Notification title="❌ El kaldırma başarısız" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,12 +430,12 @@ export class SignalRService {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`👢 kickParticipant çağrıldı: sessionId=${sessionId}, participantId=${participantId}`)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('KickParticipant', sessionId, participantId)
|
await this.connection.invoke('KickParticipant', sessionId, participantId)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error kicking participant:', error)
|
toast.push(<Notification title="❌ Katılımcı atılamadı" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,8 +449,10 @@ export class SignalRService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('ApproveHandRaise', sessionId, studentId)
|
await this.connection.invoke('ApproveHandRaise', sessionId, studentId)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error approving hand raise:', error)
|
toast.push(<Notification title="⚠️ El kaldırma onayı başarısız" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,8 +466,10 @@ export class SignalRService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('DismissHandRaise', sessionId, studentId)
|
await this.connection.invoke('DismissHandRaise', sessionId, studentId)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error dismissing hand raise:', error)
|
toast.push(<Notification title="⚠️ El indirme başarısız" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -541,8 +544,10 @@ export class SignalRService {
|
||||||
if (this.isConnected && this.currentSessionId) {
|
if (this.isConnected && this.currentSessionId) {
|
||||||
try {
|
try {
|
||||||
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.warn('LeaveClass gönderilemedi:', err)
|
toast.push(<Notification title="⚠️ Bağlantı koparılırken hata" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.connection) {
|
if (this.connection) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
|
import { toast } from '@/components/ui'
|
||||||
|
import Notification from '@/components/ui/Notification'
|
||||||
|
|
||||||
export class WebRTCService {
|
export class WebRTCService {
|
||||||
private peerConnections: Map<string, RTCPeerConnection> = new Map()
|
private peerConnections: Map<string, RTCPeerConnection> = new Map()
|
||||||
private retryCounts: Map<string, number> = new Map() // 🔑 her kullanıcı için retry sayacı
|
private retryCounts: Map<string, number> = new Map()
|
||||||
private maxRetries = 3 // 🔑 maksimum yeniden deneme sayısı
|
private maxRetries = 3
|
||||||
private signalRService: any // 👈 dışarıdan set edilecek SignalR servisi
|
private signalRService: any
|
||||||
private sessionId: string = '' // oturum için de lazım olabilir
|
private sessionId: string = ''
|
||||||
|
|
||||||
private localStream: MediaStream | null = null
|
private localStream: MediaStream | null = null
|
||||||
private onRemoteStream?: (userId: string, stream: MediaStream) => void
|
private onRemoteStream?: (userId: string, stream: MediaStream) => void
|
||||||
|
|
@ -17,23 +20,8 @@ export class WebRTCService {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
// private rtcConfiguration: RTCConfiguration = {
|
|
||||||
// iceServers: [
|
|
||||||
// { urls: 'stun:stun.l.google.com:19302' }, // STUN
|
|
||||||
// {
|
|
||||||
// urls: ['turn:your-server-ip:3478?transport=udp', 'turn:your-server-ip:3478?transport=tcp'],
|
|
||||||
// username: 'kurs', // static user/pass kullanmak istersen
|
|
||||||
// credential: 'kurs12345',
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local stream'i başlatır. Kamera/mikrofon ayarlarını parametreden alır.
|
|
||||||
*/
|
|
||||||
async initializeLocalStream(enableAudio: boolean, enableVideo: boolean): Promise<MediaStream> {
|
async initializeLocalStream(enableAudio: boolean, enableVideo: boolean): Promise<MediaStream> {
|
||||||
try {
|
try {
|
||||||
// her zaman hem ses hem video al
|
|
||||||
this.localStream = await navigator.mediaDevices.getUserMedia({
|
this.localStream = await navigator.mediaDevices.getUserMedia({
|
||||||
video: {
|
video: {
|
||||||
width: { ideal: 1280 },
|
width: { ideal: 1280 },
|
||||||
|
|
@ -47,23 +35,26 @@ export class WebRTCService {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// başlangıç ayarlarını uygula
|
|
||||||
this.localStream.getAudioTracks().forEach((track) => (track.enabled = enableAudio))
|
this.localStream.getAudioTracks().forEach((track) => (track.enabled = enableAudio))
|
||||||
this.localStream.getVideoTracks().forEach((track) => (track.enabled = enableVideo))
|
this.localStream.getVideoTracks().forEach((track) => (track.enabled = enableVideo))
|
||||||
|
|
||||||
return this.localStream
|
return this.localStream
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Error accessing media devices:', error)
|
toast.push(
|
||||||
throw error
|
<Notification
|
||||||
|
title="❌ Kamera/Mikrofon erişilemedi. Tarayıcı ayarlarınızı veya izinleri kontrol edin."
|
||||||
|
type="danger"
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
throw new Error('Media devices access failed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPeerConnection(userId: string): Promise<RTCPeerConnection> {
|
async createPeerConnection(userId: string): Promise<RTCPeerConnection> {
|
||||||
const peerConnection = new RTCPeerConnection(this.rtcConfiguration)
|
const peerConnection = new RTCPeerConnection(this.rtcConfiguration)
|
||||||
this.peerConnections.set(userId, peerConnection)
|
this.peerConnections.set(userId, peerConnection)
|
||||||
this.retryCounts.set(userId, 0) // bağlantı başında sıfırla
|
this.retryCounts.set(userId, 0)
|
||||||
|
|
||||||
// Eğer local stream varsa track'leri ekle
|
|
||||||
if (this.localStream) {
|
if (this.localStream) {
|
||||||
this.localStream.getTracks().forEach((track) => {
|
this.localStream.getTracks().forEach((track) => {
|
||||||
peerConnection.addTrack(track, this.localStream!)
|
peerConnection.addTrack(track, this.localStream!)
|
||||||
|
|
@ -83,7 +74,6 @@ export class WebRTCService {
|
||||||
|
|
||||||
peerConnection.onconnectionstatechange = async () => {
|
peerConnection.onconnectionstatechange = async () => {
|
||||||
const state = peerConnection.connectionState
|
const state = peerConnection.connectionState
|
||||||
console.log(`Bağlantı durumu [${userId}]: ${state}`)
|
|
||||||
|
|
||||||
if (state === 'closed') {
|
if (state === 'closed') {
|
||||||
this.closePeerConnection(userId)
|
this.closePeerConnection(userId)
|
||||||
|
|
@ -92,28 +82,38 @@ export class WebRTCService {
|
||||||
if (state === 'failed') {
|
if (state === 'failed') {
|
||||||
let retries = this.retryCounts.get(userId) ?? 0
|
let retries = this.retryCounts.get(userId) ?? 0
|
||||||
if (retries < this.maxRetries) {
|
if (retries < this.maxRetries) {
|
||||||
console.warn(
|
toast.push(
|
||||||
`⚠️ Bağlantı failed oldu, ICE restart deneniyor [${userId}] (Deneme ${retries + 1})`,
|
<Notification
|
||||||
|
title={`⚠️ Bağlantı başarısız, yeniden deneniyor (${retries + 1}/${this.maxRetries})`}
|
||||||
|
type="warning"
|
||||||
|
/>,
|
||||||
)
|
)
|
||||||
this.retryCounts.set(userId, retries + 1)
|
this.retryCounts.set(userId, retries + 1)
|
||||||
await this.restartIce(peerConnection, userId)
|
await this.restartIce(peerConnection, userId)
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
toast.push(
|
||||||
`❌ Bağlantı ${this.maxRetries} denemede başarısız [${userId}], peer kapatılıyor.`,
|
<Notification
|
||||||
|
title={`❌ Bağlantı kurulamadı (${this.maxRetries} deneme başarısız).`}
|
||||||
|
type="danger"
|
||||||
|
/>,
|
||||||
|
{ placement: 'top-center' },
|
||||||
)
|
)
|
||||||
this.closePeerConnection(userId)
|
this.closePeerConnection(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// En sona ekle
|
|
||||||
if (this.candidateBuffer.has(userId)) {
|
if (this.candidateBuffer.has(userId)) {
|
||||||
for (const cand of this.candidateBuffer.get(userId)!) {
|
for (const cand of this.candidateBuffer.get(userId)!) {
|
||||||
try {
|
try {
|
||||||
await peerConnection.addIceCandidate(cand)
|
await peerConnection.addIceCandidate(cand)
|
||||||
console.log(`Buffered ICE candidate eklendi [${userId}]`)
|
} catch {
|
||||||
} catch (err) {
|
toast.push(
|
||||||
console.warn(`Buffered candidate eklenemedi [${userId}]:`, err)
|
<Notification
|
||||||
|
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
||||||
|
type="warning"
|
||||||
|
/>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.candidateBuffer.delete(userId)
|
this.candidateBuffer.delete(userId)
|
||||||
|
|
@ -139,9 +139,11 @@ export class WebRTCService {
|
||||||
const offer = await pc.createOffer()
|
const offer = await pc.createOffer()
|
||||||
await pc.setLocalDescription(offer)
|
await pc.setLocalDescription(offer)
|
||||||
return offer
|
return offer
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('Offer oluşturulurken hata:', err)
|
toast.push(<Notification title="❌ Offer oluşturulamadı" type="danger" />, {
|
||||||
throw err
|
placement: 'top-center',
|
||||||
|
})
|
||||||
|
throw new Error('Offer creation failed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,44 +159,46 @@ export class WebRTCService {
|
||||||
const answer = await pc.createAnswer()
|
const answer = await pc.createAnswer()
|
||||||
await pc.setLocalDescription(answer)
|
await pc.setLocalDescription(answer)
|
||||||
return answer
|
return answer
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('Answer oluşturulurken hata:', err)
|
toast.push(<Notification title="❌ Answer oluşturulamadı" type="danger" />, {
|
||||||
throw err
|
placement: 'top-center',
|
||||||
|
})
|
||||||
|
throw new Error('Answer creation failed')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAnswer(userId: string, answer: RTCSessionDescriptionInit): Promise<void> {
|
async handleAnswer(userId: string, answer: RTCSessionDescriptionInit): Promise<void> {
|
||||||
const peerConnection = this.peerConnections.get(userId)
|
const peerConnection = this.peerConnections.get(userId)
|
||||||
if (!peerConnection) throw new Error('Peer connection not found')
|
if (!peerConnection) throw new Error('Peer connection not found')
|
||||||
|
|
||||||
await peerConnection.setRemoteDescription(answer)
|
await peerConnection.setRemoteDescription(answer)
|
||||||
}
|
}
|
||||||
|
|
||||||
async addIceCandidate(userId: string, candidate: RTCIceCandidateInit): Promise<void> {
|
async addIceCandidate(userId: string, candidate: RTCIceCandidateInit): Promise<void> {
|
||||||
const pc = this.peerConnections.get(userId)
|
const pc = this.peerConnections.get(userId)
|
||||||
if (!pc) {
|
if (!pc) {
|
||||||
// Peer yoksa buffer’a at
|
|
||||||
if (!this.candidateBuffer.has(userId)) {
|
if (!this.candidateBuffer.has(userId)) {
|
||||||
this.candidateBuffer.set(userId, [])
|
this.candidateBuffer.set(userId, [])
|
||||||
}
|
}
|
||||||
this.candidateBuffer.get(userId)!.push(candidate)
|
this.candidateBuffer.get(userId)!.push(candidate)
|
||||||
console.warn(`ICE candidate bufferlandı [${userId}]`)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pc.signalingState === 'stable' || pc.signalingState === 'have-remote-offer') {
|
if (pc.signalingState === 'stable' || pc.signalingState === 'have-remote-offer') {
|
||||||
try {
|
try {
|
||||||
await pc.addIceCandidate(candidate)
|
await pc.addIceCandidate(candidate)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.warn(`ICE candidate eklenemedi [${userId}]:`, err)
|
toast.push(
|
||||||
|
<Notification
|
||||||
|
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
||||||
|
type="warning"
|
||||||
|
/>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// signalling hazır değilse → buffer’a at
|
|
||||||
if (!this.candidateBuffer.has(userId)) {
|
if (!this.candidateBuffer.has(userId)) {
|
||||||
this.candidateBuffer.set(userId, [])
|
this.candidateBuffer.set(userId, [])
|
||||||
}
|
}
|
||||||
this.candidateBuffer.get(userId)!.push(candidate)
|
this.candidateBuffer.get(userId)!.push(candidate)
|
||||||
console.warn(`ICE candidate bufferlandı [${userId}], state=${pc.signalingState}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,8 +227,10 @@ export class WebRTCService {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('Video açılırken hata:', err)
|
toast.push(<Notification title="❌ Kamera açılamadı" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,8 +256,10 @@ export class WebRTCService {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error('Audio açılırken hata:', err)
|
toast.push(<Notification title="❌ Mikrofon açılamadı" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -264,17 +272,18 @@ export class WebRTCService {
|
||||||
try {
|
try {
|
||||||
const offer = await peerConnection.createOffer({ iceRestart: true })
|
const offer = await peerConnection.createOffer({ iceRestart: true })
|
||||||
await peerConnection.setLocalDescription(offer)
|
await peerConnection.setLocalDescription(offer)
|
||||||
console.log(`ICE restart başlatıldı [${userId}]`)
|
|
||||||
|
|
||||||
// 🔑 SignalR üzerinden karşı tarafa gönder
|
|
||||||
if (this.signalRService) {
|
if (this.signalRService) {
|
||||||
await this.signalRService.sendOffer(this.sessionId, userId, offer)
|
await this.signalRService.sendOffer(this.sessionId, userId, offer)
|
||||||
console.log(`ICE restart offer karşıya gönderildi [${userId}]`)
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ SignalR servisi bağlı değil, offer gönderilemedi')
|
toast.push(<Notification title="⚠️ Tekrar bağlanma başarısız" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.error(`ICE restart başarısız [${userId}]:`, err)
|
toast.push(<Notification title="❌ ICE restart başarısız" type="danger" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +293,7 @@ export class WebRTCService {
|
||||||
peerConnection.getSenders().forEach((sender) => sender.track?.stop())
|
peerConnection.getSenders().forEach((sender) => sender.track?.stop())
|
||||||
peerConnection.close()
|
peerConnection.close()
|
||||||
this.peerConnections.delete(userId)
|
this.peerConnections.delete(userId)
|
||||||
this.retryCounts.delete(userId) // sayaç temizle
|
this.retryCounts.delete(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,7 +320,6 @@ export class WebRTCService {
|
||||||
const alreadyHas = pc.getSenders().some((s) => s.track?.id === track.id)
|
const alreadyHas = pc.getSenders().some((s) => s.track?.id === track.id)
|
||||||
if (!alreadyHas) {
|
if (!alreadyHas) {
|
||||||
pc.addTrack(track, stream)
|
pc.addTrack(track, stream)
|
||||||
// 🔑 track bittiğinde otomatik sil
|
|
||||||
track.onended = () => {
|
track.onended = () => {
|
||||||
this.removeTrackFromPeers(track)
|
this.removeTrackFromPeers(track)
|
||||||
}
|
}
|
||||||
|
|
@ -326,8 +334,10 @@ export class WebRTCService {
|
||||||
if (sender.track === track) {
|
if (sender.track === track) {
|
||||||
try {
|
try {
|
||||||
pc.removeTrack(sender)
|
pc.removeTrack(sender)
|
||||||
} catch (err) {
|
} catch {
|
||||||
console.warn('removeTrack hata verdi:', err)
|
toast.push(<Notification title="⚠️ Track silinemedi" type="warning" />, {
|
||||||
|
placement: 'top-center',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (sender.track?.readyState !== 'ended') {
|
if (sender.track?.readyState !== 'ended') {
|
||||||
sender.track?.stop()
|
sender.track?.stop()
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,6 @@ const RoomDetail: React.FC = () => {
|
||||||
|
|
||||||
// Setup WebRTC remote stream handler
|
// Setup WebRTC remote stream handler
|
||||||
webRTCServiceRef.current.onRemoteStreamReceived((userId, stream) => {
|
webRTCServiceRef.current.onRemoteStreamReceived((userId, stream) => {
|
||||||
console.log('Received remote stream from:', userId)
|
|
||||||
setParticipants((prev) => prev.map((p) => (p.id === userId ? { ...p, stream } : p)))
|
setParticipants((prev) => prev.map((p) => (p.id === userId ? { ...p, stream } : p)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -285,7 +284,6 @@ const RoomDetail: React.FC = () => {
|
||||||
if (remoteUserId === user.id) return
|
if (remoteUserId === user.id) return
|
||||||
if (!isActive) return
|
if (!isActive) return
|
||||||
|
|
||||||
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
|
|
||||||
toast.push(<Notification title={`${name} sınıfa katıldı`} type="success" />, {
|
toast.push(<Notification title={`${name} sınıfa katıldı`} type="success" />, {
|
||||||
placement: 'top-center',
|
placement: 'top-center',
|
||||||
})
|
})
|
||||||
|
|
@ -366,11 +364,14 @@ const RoomDetail: React.FC = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
signalRServiceRef.current.setParticipantLeaveHandler(({ userId, sessionId }) => {
|
signalRServiceRef.current.setParticipantLeaveHandler(({ userId, sessionId }) => {
|
||||||
console.log(`👋 Participant left handler: ${userId}, sessionId=${sessionId}`)
|
if (userId !== user.id) {
|
||||||
|
const leftUser = participants.find((p) => p.id === userId)
|
||||||
|
const leftName = leftUser ? leftUser.name : 'Bilinmeyen'
|
||||||
|
|
||||||
toast.push(<Notification title={`Katılımcı ayrıldı: ${participants.find((p) => p.id === userId)?.name}`} type="warning" />, {
|
toast.push(<Notification title={`Katılımcı ayrıldı: ${leftName}`} type="warning" />, {
|
||||||
placement: 'top-center',
|
placement: 'top-center',
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// peer connection’ı kapat
|
// peer connection’ı kapat
|
||||||
webRTCServiceRef.current?.closePeerConnection(userId)
|
webRTCServiceRef.current?.closePeerConnection(userId)
|
||||||
|
|
@ -438,7 +439,13 @@ const RoomDetail: React.FC = () => {
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize services:', error)
|
toast.push(
|
||||||
|
<Notification
|
||||||
|
title="❌ Sınıf servisleri başlatılamadı. Bağlantınızı veya tarayıcı izinlerini kontrol edin."
|
||||||
|
type="danger"
|
||||||
|
/>,
|
||||||
|
{ placement: 'top-center' },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -476,7 +483,7 @@ const RoomDetail: React.FC = () => {
|
||||||
// Başka sayfaya yönlendir
|
// Başka sayfaya yönlendir
|
||||||
navigate(ROUTES_ENUM.protected.admin.classroom.classes)
|
navigate(ROUTES_ENUM.protected.admin.classroom.classes)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Leave işlemi sırasında hata:', err)
|
toast.push(<Notification title="⚠️ Çıkış sırasında hata oluştu" type="warning" />)
|
||||||
navigate(ROUTES_ENUM.protected.admin.classroom.classes)
|
navigate(ROUTES_ENUM.protected.admin.classroom.classes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -485,6 +492,7 @@ const RoomDetail: React.FC = () => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (newMessage.trim() && signalRServiceRef.current) {
|
if (newMessage.trim() && signalRServiceRef.current) {
|
||||||
if (messageMode === 'private' && selectedRecipient) {
|
if (messageMode === 'private' && selectedRecipient) {
|
||||||
|
try {
|
||||||
await signalRServiceRef.current.sendPrivateMessage(
|
await signalRServiceRef.current.sendPrivateMessage(
|
||||||
classSession.id,
|
classSession.id,
|
||||||
user.id,
|
user.id,
|
||||||
|
|
@ -494,7 +502,11 @@ const RoomDetail: React.FC = () => {
|
||||||
selectedRecipient.name,
|
selectedRecipient.name,
|
||||||
user.role === 'teacher',
|
user.role === 'teacher',
|
||||||
)
|
)
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(<Notification title="❌ Özel mesaj gönderilemedi" type="danger" />)
|
||||||
|
}
|
||||||
} else if (messageMode === 'announcement' && user.role === 'teacher') {
|
} else if (messageMode === 'announcement' && user.role === 'teacher') {
|
||||||
|
try {
|
||||||
await signalRServiceRef.current.sendAnnouncement(
|
await signalRServiceRef.current.sendAnnouncement(
|
||||||
classSession.id,
|
classSession.id,
|
||||||
user.id,
|
user.id,
|
||||||
|
|
@ -502,7 +514,11 @@ const RoomDetail: React.FC = () => {
|
||||||
newMessage.trim(),
|
newMessage.trim(),
|
||||||
user.role === 'teacher',
|
user.role === 'teacher',
|
||||||
)
|
)
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(<Notification title="❌ Duyuru gönderilemedi" type="danger" />)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
await signalRServiceRef.current.sendChatMessage(
|
await signalRServiceRef.current.sendChatMessage(
|
||||||
classSession.id,
|
classSession.id,
|
||||||
user.id,
|
user.id,
|
||||||
|
|
@ -510,6 +526,9 @@ const RoomDetail: React.FC = () => {
|
||||||
newMessage.trim(),
|
newMessage.trim(),
|
||||||
user.role === 'teacher',
|
user.role === 'teacher',
|
||||||
)
|
)
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(<Notification title="❌ Genel mesaj gönderilemedi" type="danger" />)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setNewMessage('')
|
setNewMessage('')
|
||||||
}
|
}
|
||||||
|
|
@ -521,12 +540,16 @@ const RoomDetail: React.FC = () => {
|
||||||
isTeacher: boolean,
|
isTeacher: boolean,
|
||||||
) => {
|
) => {
|
||||||
if (signalRServiceRef.current && user.role === 'teacher') {
|
if (signalRServiceRef.current && user.role === 'teacher') {
|
||||||
|
try {
|
||||||
await signalRServiceRef.current.muteParticipant(
|
await signalRServiceRef.current.muteParticipant(
|
||||||
classSession.id,
|
classSession.id,
|
||||||
participantId,
|
participantId,
|
||||||
isMuted,
|
isMuted,
|
||||||
isTeacher,
|
isTeacher,
|
||||||
)
|
)
|
||||||
|
} catch (err) {
|
||||||
|
toast.push(<Notification title="❌ Katılımcı susturulamadı" type="danger" />)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -563,8 +586,7 @@ const RoomDetail: React.FC = () => {
|
||||||
|
|
||||||
const handleKickParticipant = async (participantId: string) => {
|
const handleKickParticipant = async (participantId: string) => {
|
||||||
if (signalRServiceRef.current && user.role === 'teacher') {
|
if (signalRServiceRef.current && user.role === 'teacher') {
|
||||||
console.log(`👢 handleKickParticipant UI’den çağrıldı: ${participantId}`)
|
try {
|
||||||
|
|
||||||
await signalRServiceRef.current.kickParticipant(classSession.id, participantId)
|
await signalRServiceRef.current.kickParticipant(classSession.id, participantId)
|
||||||
setAttendanceRecords((prev) =>
|
setAttendanceRecords((prev) =>
|
||||||
prev.map((r) => {
|
prev.map((r) => {
|
||||||
|
|
@ -581,6 +603,9 @@ const RoomDetail: React.FC = () => {
|
||||||
return r
|
return r
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
} catch (error) {
|
||||||
|
toast.push(<Notification title="❌ Katılımcı atılamadı" type="danger" />)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -619,7 +644,12 @@ const RoomDetail: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
mic = await navigator.mediaDevices.getUserMedia({ audio: true })
|
mic = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Mic alınamadı, sadece ekran paylaşılacak', err)
|
toast.push(
|
||||||
|
<Notification
|
||||||
|
title="⚠️ Mikrofon alınamadı. Sadece ekran paylaşımı yapılacak."
|
||||||
|
type="warning"
|
||||||
|
/>,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. merge et
|
// 3. merge et
|
||||||
|
|
@ -637,7 +667,7 @@ const RoomDetail: React.FC = () => {
|
||||||
handleStopScreenShare()
|
handleStopScreenShare()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting screen share:', error)
|
toast.push(<Notification title="❌ Ekran paylaşımı başlatılamadı" type="danger" />)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue