live-sfu-demo/internal/server/playlist.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)
}