158 lines
5.1 KiB
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
|
|
}
|