Initial commit: party-mix-app with prefetch cache, audio preload optimizations
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
162
apps/backend/internal/handlers/remote.go
Normal file
162
apps/backend/internal/handlers/remote.go
Normal file
@@ -0,0 +1,162 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user