This commit is contained in:
@@ -57,18 +57,20 @@ type WsHub struct {
|
||||
students map[int64]*SocketClient
|
||||
teachers map[string]*SocketClient
|
||||
teachersByStaff map[uint][]string // staffId -> teacher connection addresses
|
||||
subscribers map[int64][]string // studentId -> list of teacher connection addresses
|
||||
subscribers map[int64]map[string]string // studentId -> teacher connection address -> mode ("grid" | "focus")
|
||||
grace map[int64]offlineGrace
|
||||
graceTimers map[int64]*time.Timer
|
||||
lastRelay map[string]time.Time // studentID:event -> last relay time
|
||||
}
|
||||
|
||||
var Hub = &WsHub{
|
||||
students: make(map[int64]*SocketClient),
|
||||
teachers: make(map[string]*SocketClient),
|
||||
teachersByStaff: make(map[uint][]string),
|
||||
subscribers: make(map[int64][]string),
|
||||
subscribers: make(map[int64]map[string]string),
|
||||
grace: make(map[int64]offlineGrace),
|
||||
graceTimers: make(map[int64]*time.Timer),
|
||||
lastRelay: make(map[string]time.Time),
|
||||
}
|
||||
|
||||
func (h *WsHub) IsStudentOnline(studentRkID int64) bool {
|
||||
@@ -171,7 +173,7 @@ func (h *WsHub) ForceStudentOffline(studentID int64) {
|
||||
log.Printf("[WS] Student %d force offline (intentional quit/violation)", studentID)
|
||||
h.broadcastPresence(studentID, false, classID)
|
||||
if teachers, exists := h.subscribers[studentID]; exists {
|
||||
for _, tAddr := range teachers {
|
||||
for tAddr := range teachers {
|
||||
if t, found := h.teachers[tAddr]; found {
|
||||
_ = t.WriteJSON(SocketMsg{
|
||||
Event: "teacher:stream-stopped",
|
||||
@@ -207,7 +209,7 @@ func (h *WsHub) finalizeStudentOffline(studentID int64, classID int64) {
|
||||
h.broadcastPresence(studentID, false, classID)
|
||||
|
||||
if teachers, exists := h.subscribers[studentID]; exists {
|
||||
for _, tAddr := range teachers {
|
||||
for tAddr := range teachers {
|
||||
if t, found := h.teachers[tAddr]; found {
|
||||
_ = t.WriteJSON(SocketMsg{
|
||||
Event: "teacher:stream-stopped",
|
||||
@@ -256,10 +258,8 @@ func (h *WsHub) Register(c *SocketClient) {
|
||||
h.students[c.StudentID] = c
|
||||
log.Printf("[WS] Student %d registered (Address: %s, Class: %d, reconnectGrace=%v)", c.StudentID, c.Addr, c.ClassID, wasInGrace)
|
||||
h.broadcastPresence(c.StudentID, true, c.ClassID)
|
||||
if subs, exists := h.subscribers[c.StudentID]; exists && len(subs) > 0 {
|
||||
_ = c.WriteJSON(SocketMsg{Event: "start_screenshot_stream"})
|
||||
_ = c.WriteJSON(SocketMsg{Event: "start_webcam_stream"})
|
||||
log.Printf("[WS] Student %d has active subscribers. Sent start stream commands.", c.StudentID)
|
||||
if _, exists := h.subscribers[c.StudentID]; exists {
|
||||
h.updateStudentStreamModeLocked(c.StudentID)
|
||||
}
|
||||
} else if c.Role == "teacher" {
|
||||
h.teachers[c.Addr] = c
|
||||
@@ -310,66 +310,88 @@ func (h *WsHub) Unregister(c *SocketClient) {
|
||||
}
|
||||
log.Printf("[WS] Teacher %s disconnected", c.Addr)
|
||||
|
||||
for sID, teachersList := range h.subscribers {
|
||||
newList := []string{}
|
||||
for _, addr := range teachersList {
|
||||
if addr != c.Addr {
|
||||
newList = append(newList, addr)
|
||||
}
|
||||
}
|
||||
if len(newList) == 0 {
|
||||
for sID, teachersMap := range h.subscribers {
|
||||
delete(teachersMap, c.Addr)
|
||||
if len(teachersMap) == 0 {
|
||||
delete(h.subscribers, sID)
|
||||
if student, exists := h.students[sID]; exists {
|
||||
_ = student.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"})
|
||||
_ = student.WriteJSON(SocketMsg{Event: "stop_webcam_stream"})
|
||||
}
|
||||
} else {
|
||||
h.subscribers[sID] = newList
|
||||
h.updateStudentStreamModeLocked(sID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WsHub) Subscribe(teacherAddr string, studentID int64) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
func (h *WsHub) updateStudentStreamModeLocked(studentID int64) {
|
||||
student, exists := h.students[studentID]
|
||||
if !exists || student == nil {
|
||||
return
|
||||
}
|
||||
|
||||
teachersList := h.subscribers[studentID]
|
||||
alreadySubscribed := false
|
||||
for _, addr := range teachersList {
|
||||
if addr == teacherAddr {
|
||||
alreadySubscribed = true
|
||||
teachersMap, ok := h.subscribers[studentID]
|
||||
if !ok || len(teachersMap) == 0 {
|
||||
_ = student.WriteJSON(SocketMsg{Event: "stop_screenshot_stream"})
|
||||
_ = student.WriteJSON(SocketMsg{Event: "stop_webcam_stream"})
|
||||
return
|
||||
}
|
||||
|
||||
// Determine effective mode: if any teacher wants "focus", use focus. Otherwise "grid".
|
||||
effectiveMode := "grid"
|
||||
for _, mode := range teachersMap {
|
||||
if mode == "focus" {
|
||||
effectiveMode = "focus"
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadySubscribed {
|
||||
h.subscribers[studentID] = append(teachersList, teacherAddr)
|
||||
log.Printf("[WS] Teacher %s subscribed to student %d stream", teacherAddr, studentID)
|
||||
|
||||
intervalMs := 3000 // default for grid
|
||||
if effectiveMode == "focus" {
|
||||
intervalMs = 500 // 500ms for focus view
|
||||
}
|
||||
|
||||
if student, exists := h.students[studentID]; exists {
|
||||
_ = student.WriteJSON(SocketMsg{Event: "start_screenshot_stream"})
|
||||
_ = student.WriteJSON(SocketMsg{Event: "start_webcam_stream"})
|
||||
_ = student.WriteJSON(SocketMsg{
|
||||
Event: "start_screenshot_stream",
|
||||
Data: map[string]any{"interval": intervalMs},
|
||||
})
|
||||
_ = student.WriteJSON(SocketMsg{
|
||||
Event: "start_webcam_stream",
|
||||
Data: map[string]any{"interval": intervalMs},
|
||||
})
|
||||
log.Printf("[WS] Updated student %d stream mode to %s (interval: %dms)", studentID, effectiveMode, intervalMs)
|
||||
}
|
||||
|
||||
func (h *WsHub) Subscribe(teacherAddr string, studentID int64, mode string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
teachersMap, exists := h.subscribers[studentID]
|
||||
if !exists {
|
||||
teachersMap = make(map[string]string)
|
||||
h.subscribers[studentID] = teachersMap
|
||||
}
|
||||
|
||||
teachersMap[teacherAddr] = mode
|
||||
log.Printf("[WS] Teacher %s subscribed to student %d stream in %s mode", teacherAddr, studentID, mode)
|
||||
|
||||
h.updateStudentStreamModeLocked(studentID)
|
||||
}
|
||||
|
||||
func (h *WsHub) Unsubscribe(teacherAddr string, studentID int64) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
teachersList, exists := h.subscribers[studentID]
|
||||
teachersMap, exists := h.subscribers[studentID]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
newList := []string{}
|
||||
for _, addr := range teachersList {
|
||||
if addr != teacherAddr {
|
||||
newList = append(newList, addr)
|
||||
}
|
||||
}
|
||||
delete(teachersMap, teacherAddr)
|
||||
log.Printf("[WS] Teacher %s unsubscribed from student %d stream", teacherAddr, studentID)
|
||||
|
||||
if len(newList) == 0 {
|
||||
if len(teachersMap) == 0 {
|
||||
delete(h.subscribers, studentID)
|
||||
log.Printf("[WS] Student %d has no more proctor subscribers. Stopping streams.", studentID)
|
||||
if student, exists := h.students[studentID]; exists {
|
||||
@@ -377,20 +399,46 @@ func (h *WsHub) Unsubscribe(teacherAddr string, studentID int64) {
|
||||
_ = student.WriteJSON(SocketMsg{Event: "stop_webcam_stream"})
|
||||
}
|
||||
} else {
|
||||
h.subscribers[studentID] = newList
|
||||
h.updateStudentStreamModeLocked(studentID)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WsHub) RelayFrame(studentID int64, event string, data map[string]any) {
|
||||
h.mu.RLock()
|
||||
teachersList, exists := h.subscribers[studentID]
|
||||
if !exists || len(teachersList) == 0 {
|
||||
h.mu.RUnlock()
|
||||
h.mu.Lock()
|
||||
teachersMap, exists := h.subscribers[studentID]
|
||||
if !exists || len(teachersMap) == 0 {
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
addrs := make([]string, len(teachersList))
|
||||
copy(addrs, teachersList)
|
||||
h.mu.RUnlock()
|
||||
|
||||
// Compute effective mode/interval
|
||||
effectiveMode := "grid"
|
||||
for _, mode := range teachersMap {
|
||||
if mode == "focus" {
|
||||
effectiveMode = "focus"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
minInterval := 3000 * time.Millisecond
|
||||
if effectiveMode == "focus" {
|
||||
minInterval = 500 * time.Millisecond
|
||||
}
|
||||
|
||||
relayKey := strconv.FormatInt(studentID, 10) + ":" + event
|
||||
lastTime := h.lastRelay[relayKey]
|
||||
now := time.Now()
|
||||
if now.Sub(lastTime) < minInterval {
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
h.lastRelay[relayKey] = now
|
||||
|
||||
addrs := make([]string, 0, len(teachersMap))
|
||||
for addr := range teachersMap {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
relayEvent := "teacher:screenshot-stream-frame"
|
||||
if event == "webcam_stream_frame" {
|
||||
@@ -519,8 +567,12 @@ func WebSocketHandler(db *gorm.DB) func(*websocket.Conn) {
|
||||
case string:
|
||||
sID, _ = strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
mode, _ := msg.Data["mode"].(string)
|
||||
if mode == "" {
|
||||
mode = "focus"
|
||||
}
|
||||
if sID > 0 {
|
||||
Hub.Subscribe(client.Addr, sID)
|
||||
Hub.Subscribe(client.Addr, sID, mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user