package server import ( "encoding/json" "net/http" "strconv" "context" "sync-live/internal/room" ) // handleRoomPlaylist 处理房间播放列表的 CRUD。 func (s *Server) handleRoomPlaylist(w http.ResponseWriter, r *http.Request) { roomName := r.PathValue("room") if roomName == "" { http.Error(w, "room required", http.StatusBadRequest) return } if s.roomSvc == nil || s.roomSvc.Store().DB() == nil { http.Error(w, "room service not configured", http.StatusServiceUnavailable) return } switch r.Method { case http.MethodGet: items, err := s.roomSvc.Store().ListPlaylistItems(r.Context(), roomName) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if items == nil { items = []*room.PlaylistItem{} } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"items": items}) case http.MethodPost: user := actorName(r) if user == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } if err := s.roomSvc.EnsurePermission(r.Context(), roomName, user, room.PermControlStream); err != nil { http.Error(w, err.Error(), http.StatusForbidden) return } var body struct { URL string `json:"url"` Title string `json:"title"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.URL == "" { http.Error(w, "url required", http.StatusBadRequest) return } item := &room.PlaylistItem{ RoomName: roomName, URL: body.URL, Title: body.Title, AddedBy: user, } created, err := s.roomSvc.Store().AddPlaylistItem(r.Context(), item) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 通过 WebSocket 广播播放列表变更 s.broadcastPlaylistUpdate(r.Context(), roomName) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(created) case http.MethodDelete: user := actorName(r) if user == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } if err := s.roomSvc.EnsurePermission(r.Context(), roomName, user, room.PermControlStream); err != nil { http.Error(w, err.Error(), http.StatusForbidden) return } itemIDStr := r.URL.Query().Get("id") itemID, err := strconv.ParseInt(itemIDStr, 10, 64) if err != nil || itemID <= 0 { http.Error(w, "valid item id required", http.StatusBadRequest) return } if err := s.roomSvc.Store().RemovePlaylistItem(r.Context(), roomName, itemID); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.broadcastPlaylistUpdate(r.Context(), roomName) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{"ok": true}) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } // handleRoomPlayback 处理同步播放状态的读取和更新。 func (s *Server) handleRoomPlayback(w http.ResponseWriter, r *http.Request) { roomName := r.PathValue("room") if roomName == "" { http.Error(w, "room required", http.StatusBadRequest) return } if s.roomSvc == nil || s.roomSvc.Store().DB() == nil { http.Error(w, "room service not configured", http.StatusServiceUnavailable) return } switch r.Method { case http.MethodGet: ps, err := s.roomSvc.Store().GetPlaybackState(r.Context(), roomName) if err != nil { // 无状态时返回默认值 ps = &room.PlaybackState{Room: roomName, Rate: 1} } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(ps) case http.MethodPost, http.MethodPut: user := actorName(r) if user == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } if err := s.roomSvc.EnsurePermission(r.Context(), roomName, user, room.PermControlStream); err != nil { http.Error(w, err.Error(), http.StatusForbidden) return } var body struct { Position float64 `json:"position"` Playing bool `json:"playing"` Rate float64 `json:"rate"` CurrentItem int64 `json:"current_item_id"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "bad json", http.StatusBadRequest) return } if body.Rate <= 0 { body.Rate = 1 } ps := &room.PlaybackState{ Room: roomName, Position: body.Position, Playing: body.Playing, Rate: body.Rate, CurrentItem: body.CurrentItem, UpdatedBy: user, } if err := s.roomSvc.Store().SavePlaybackState(r.Context(), ps); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 通过 WebSocket 广播播放状态变更 s.hub.broadcastEvent(roomName, "playback", ps) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(ps) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } // broadcastPlaylistUpdate 向房间广播播放列表变更事件。 func (s *Server) broadcastPlaylistUpdate(ctx context.Context, roomName string) { items, _ := s.roomSvc.Store().ListPlaylistItems(ctx, roomName) s.hub.broadcastEvent(roomName, "playlist_update", items) }