package handlers import ( "net/http" "sync" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type RemoteQueueItem struct { Title string `json:"title"` Owner string `json:"owner"` ColorBg string `json:"color_bg"` ColorText string `json:"color_text"` Img string `json:"img,omitempty"` } type RemoteVersion struct { Title string `json:"title"` Artist string `json:"artist"` Duration string `json:"duration"` Img string `json:"img,omitempty"` } type RemoteState struct { Title string `json:"title"` Artist string `json:"artist"` Cover string `json:"cover"` IsPlaying bool `json:"is_playing"` Volume float64 `json:"volume"` Progress float64 `json:"progress"` Duration float64 `json:"duration"` QueueLen int `json:"queue_len"` CurIdx int `json:"cur_idx"` Queue []RemoteQueueItem `json:"queue,omitempty"` Versions []RemoteVersion `json:"versions,omitempty"` ActiveVersion int `json:"active_version"` } type RemoteCommand struct { ID string `json:"id"` Cmd string `json:"cmd"` Value float64 `json:"value,omitempty"` Text string `json:"text,omitempty"` } type remoteRoom struct { state RemoteState commands []RemoteCommand lastSeen time.Time mu sync.Mutex } var ( remoteMu sync.RWMutex remoteRooms = map[string]*remoteRoom{} ) func init() { go func() { for { time.Sleep(30 * time.Minute) remoteMu.Lock() for id, r := range remoteRooms { if time.Since(r.lastSeen) > 4*time.Hour { delete(remoteRooms, id) } } remoteMu.Unlock() } }() } func CreateRemoteRoom(c *gin.Context) { id := uuid.NewString()[:8] remoteMu.Lock() remoteRooms[id] = &remoteRoom{lastSeen: time.Now()} remoteMu.Unlock() c.JSON(http.StatusOK, gin.H{"id": id}) } func PushRemoteState(c *gin.Context) { id := c.Param("id") remoteMu.RLock() room, ok := remoteRooms[id] remoteMu.RUnlock() if !ok { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } var s RemoteState if err := c.ShouldBindJSON(&s); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } room.mu.Lock() room.state = s room.lastSeen = time.Now() room.mu.Unlock() c.Status(http.StatusNoContent) } func GetRemoteState(c *gin.Context) { id := c.Param("id") remoteMu.RLock() room, ok := remoteRooms[id] remoteMu.RUnlock() if !ok { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } room.mu.Lock() s := room.state room.mu.Unlock() c.JSON(http.StatusOK, s) } func SendRemoteCommand(c *gin.Context) { id := c.Param("id") remoteMu.RLock() room, ok := remoteRooms[id] remoteMu.RUnlock() if !ok { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } var cmd RemoteCommand if err := c.ShouldBindJSON(&cmd); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } cmd.ID = uuid.NewString() room.mu.Lock() room.commands = append(room.commands, cmd) if len(room.commands) > 50 { room.commands = room.commands[len(room.commands)-50:] } room.mu.Unlock() c.JSON(http.StatusOK, gin.H{"ok": true}) } func PollRemoteCommands(c *gin.Context) { id := c.Param("id") remoteMu.RLock() room, ok := remoteRooms[id] remoteMu.RUnlock() if !ok { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } room.mu.Lock() cmds := room.commands room.commands = nil room.lastSeen = time.Now() room.mu.Unlock() if cmds == nil { cmds = []RemoteCommand{} } c.JSON(http.StatusOK, cmds) }