172 lines
5.0 KiB
Go
172 lines
5.0 KiB
Go
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)
|
|
}
|