live-sfu-demo/internal/room/playlist.go

158 lines
5.1 KiB
Go

package room
import (
"context"
"database/sql"
"fmt"
"time"
)
// PlaylistItem 表示房间共享播放列表中的一个媒体项。
type PlaylistItem struct {
ID int64 `json:"id"`
RoomName string `json:"room_name"`
URL string `json:"url"`
Title string `json:"title"`
AddedBy string `json:"added_by"`
SortOrder int32 `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
}
// PlaybackState 表示房间当前的同步播放状态。
type PlaybackState struct {
Room string `json:"room"`
Position float64 `json:"position"`
Playing bool `json:"playing"`
Rate float64 `json:"rate"`
CurrentItem int64 `json:"current_item_id"`
UpdatedAt int64 `json:"updated_at"`
UpdatedBy string `json:"updated_by"`
}
func InitPlaylistSchema(db *sql.DB) error {
stmts := []string{
`CREATE TABLE IF NOT EXISTS playlist_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room TEXT NOT NULL,
url TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '',
added_by TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
FOREIGN KEY (room) REFERENCES rooms_v2(name) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS idx_playlist_room ON playlist_items(room, sort_order)`,
`CREATE TABLE IF NOT EXISTS playback_state (
room TEXT PRIMARY KEY,
position REAL NOT NULL DEFAULT 0,
playing INTEGER NOT NULL DEFAULT 0,
rate REAL NOT NULL DEFAULT 1,
current_item_id INTEGER NOT NULL DEFAULT 0,
updated_at INTEGER NOT NULL,
updated_by TEXT NOT NULL DEFAULT '',
FOREIGN KEY (room) REFERENCES rooms_v2(name) ON DELETE CASCADE
)`,
}
for _, stmt := range stmts {
if _, err := db.Exec(stmt); err != nil {
return fmt.Errorf("init playlist schema: %w", err)
}
}
return nil
}
// AddPlaylistItem 向房间播放列表添加一个媒体项。
func (s *Store) AddPlaylistItem(ctx context.Context, item *PlaylistItem) (*PlaylistItem, error) {
if s.db == nil {
return nil, fmt.Errorf("no db")
}
item.CreatedAt = time.Now()
res, err := s.db.ExecContext(ctx,
`INSERT INTO playlist_items (room, url, title, added_by, sort_order, created_at) VALUES (?, ?, ?, ?, ?, ?)`,
item.RoomName, item.URL, item.Title, item.AddedBy, item.SortOrder, item.CreatedAt.Unix())
if err != nil {
return nil, err
}
id, _ := res.LastInsertId()
item.ID = id
return item, nil
}
// ListPlaylistItems 返回房间的播放列表(按排序字段排列)。
func (s *Store) ListPlaylistItems(ctx context.Context, roomName string) ([]*PlaylistItem, error) {
if s.db == nil {
return nil, nil
}
rows, err := s.db.QueryContext(ctx,
`SELECT id, room, url, title, added_by, sort_order, created_at FROM playlist_items WHERE room = ? ORDER BY sort_order ASC, id ASC`, roomName)
if err != nil {
return nil, err
}
defer rows.Close()
var out []*PlaylistItem
for rows.Next() {
var it PlaylistItem
var createdAt int64
if err := rows.Scan(&it.ID, &it.RoomName, &it.URL, &it.Title, &it.AddedBy, &it.SortOrder, &createdAt); err != nil {
return nil, err
}
it.CreatedAt = time.Unix(createdAt, 0)
out = append(out, &it)
}
return out, rows.Err()
}
// RemovePlaylistItem 删除播放列表中的一个媒体项。
func (s *Store) RemovePlaylistItem(ctx context.Context, roomName string, itemID int64) error {
if s.db == nil {
return fmt.Errorf("no db")
}
_, err := s.db.ExecContext(ctx, `DELETE FROM playlist_items WHERE room = ? AND id = ?`, roomName, itemID)
return err
}
// ReorderPlaylistItems 批量更新播放列表项的排序。
func (s *Store) ReorderPlaylistItems(ctx context.Context, roomName string, ids []int64) error {
if s.db == nil {
return fmt.Errorf("no db")
}
for i, id := range ids {
if _, err := s.db.ExecContext(ctx,
`UPDATE playlist_items SET sort_order = ? WHERE room = ? AND id = ?`, i, roomName, id); err != nil {
return err
}
}
return nil
}
// SavePlaybackState 保存或更新房间的同步播放状态。
func (s *Store) SavePlaybackState(ctx context.Context, ps *PlaybackState) error {
if s.db == nil {
return fmt.Errorf("no db")
}
ps.UpdatedAt = time.Now().Unix()
_, err := s.db.ExecContext(ctx,
`INSERT INTO playback_state (room, position, playing, rate, current_item_id, updated_at, updated_by)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(room) DO UPDATE SET position=excluded.position, playing=excluded.playing, rate=excluded.rate, current_item_id=excluded.current_item_id, updated_at=excluded.updated_at, updated_by=excluded.updated_by`,
ps.Room, ps.Position, boolToInt(ps.Playing), ps.Rate, ps.CurrentItem, ps.UpdatedAt, ps.UpdatedBy)
return err
}
// GetPlaybackState 获取房间的同步播放状态。
func (s *Store) GetPlaybackState(ctx context.Context, roomName string) (*PlaybackState, error) {
if s.db == nil {
return nil, sql.ErrNoRows
}
var ps PlaybackState
var playing int
err := s.db.QueryRowContext(ctx,
`SELECT room, position, playing, rate, current_item_id, updated_at, updated_by FROM playback_state WHERE room = ?`, roomName).
Scan(&ps.Room, &ps.Position, &playing, &ps.Rate, &ps.CurrentItem, &ps.UpdatedAt, &ps.UpdatedBy)
if err != nil {
return nil, err
}
ps.Playing = playing != 0
return &ps, nil
}