feat: store movie current status

main v0.9.7-beta.5
zijiren233 2 weeks ago
parent e60a15d3fb
commit 14c59e9fb2

@ -184,3 +184,14 @@ func SetRoomStatusByCreator(userID string, status model.RoomStatus) error {
result := db.Model(&model.Room{}).Where("creator_id = ?", userID).Update("status", status) result := db.Model(&model.Room{}).Where("creator_id = ?", userID).Update("status", status)
return HandleUpdateResult(result, ErrRoomNotFound) return HandleUpdateResult(result, ErrRoomNotFound)
} }
func SetRoomCurrent(roomID string, current *model.Current) error {
r := &model.Room{
Current: current,
}
result := db.Model(r).
Where("id = ?", roomID).
Select("Current").
Updates(r)
return HandleUpdateResult(result, ErrRoomNotFound)
}

@ -15,7 +15,7 @@ type dbVersion struct {
NextVersion string NextVersion string
} }
const CurrentVersion = "0.0.12" const CurrentVersion = "0.0.13"
var models = []any{ var models = []any{
new(model.Setting), new(model.Setting),
@ -82,6 +82,9 @@ var dbVersions = map[string]dbVersion{
NextVersion: "0.0.12", NextVersion: "0.0.12",
}, },
"0.0.12": { "0.0.12": {
NextVersion: "0.0.13",
},
"0.0.13": {
NextVersion: "", NextVersion: "",
}, },
} }

@ -0,0 +1,91 @@
package model
import "time"
type Current struct {
Movie CurrentMovie `json:"movie"`
Status Status `json:"status"`
}
type CurrentMovie struct {
ID string `json:"id,omitempty"`
IsLive bool `json:"isLive,omitempty"`
SubPath string `json:"subPath,omitempty"`
}
type Status struct {
LastUpdate time.Time `json:"lastUpdate,omitempty"`
CurrentTime float64 `json:"currentTime,omitempty"`
PlaybackRate float64 `json:"playbackRate,omitempty"`
IsPlaying bool `json:"isPlaying,omitempty"`
}
func NewStatus() Status {
return Status{
CurrentTime: 0,
PlaybackRate: 1.0,
LastUpdate: time.Now(),
}
}
func (c *Current) UpdateStatus() Status {
if c.Movie.IsLive {
c.Status.LastUpdate = time.Now()
return c.Status
}
if c.Status.IsPlaying {
c.Status.CurrentTime += time.Since(c.Status.LastUpdate).Seconds() * c.Status.PlaybackRate
}
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) setLiveStatus() Status {
c.Status.IsPlaying = true
c.Status.PlaybackRate = 1.0
c.Status.CurrentTime = 0
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
c.Status.IsPlaying = playing
c.Status.PlaybackRate = rate
if playing {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.PlaybackRate = rate
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeek(seek, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * c.Status.PlaybackRate)
} else {
c.Status.CurrentTime = seek
}
c.Status.LastUpdate = time.Now()
return c.Status
}

@ -41,6 +41,7 @@ type Room struct {
RoomMembers []*RoomMember `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` RoomMembers []*RoomMember `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []*Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Movies []*Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Status RoomStatus `gorm:"not null;default:2"` Status RoomStatus `gorm:"not null;default:2"`
Current *Current `gorm:"serializer:fastjson"`
} }
func (r *Room) BeforeCreate(tx *gorm.DB) error { func (r *Room) BeforeCreate(tx *gorm.DB) error {

@ -2,144 +2,76 @@ package op
import ( import (
"sync" "sync"
"time"
"github.com/synctv-org/synctv/internal/db"
"github.com/synctv-org/synctv/internal/model"
) )
type current struct { type current struct {
current Current roomID string
current model.Current
lock sync.RWMutex lock sync.RWMutex
} }
type Current struct { func newCurrent(roomID string, c *model.Current) *current {
Movie CurrentMovie if c == nil {
Status Status return &current{
} roomID: roomID,
current: model.Current{
type CurrentMovie struct { Status: model.NewStatus(),
ID string },
IsLive bool }
}
func newCurrent() *current {
return &current{
current: Current{
Status: newStatus(),
},
} }
} return &current{
roomID: roomID,
type Status struct { current: *c,
lastUpdate time.Time `json:"-"`
CurrentTime float64 `json:"currentTime"`
PlaybackRate float64 `json:"playbackRate"`
IsPlaying bool `json:"isPlaying"`
}
func newStatus() Status {
return Status{
CurrentTime: 0,
PlaybackRate: 1.0,
lastUpdate: time.Now(),
} }
} }
func (c *current) Current() Current { func (c *current) Current() model.Current {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
c.current.UpdateStatus() c.current.UpdateStatus()
return c.current return c.current
} }
func (c *current) SetMovie(movie CurrentMovie, play bool) { func (c *current) SubPath() string {
c.lock.RLock()
defer c.lock.RUnlock()
return c.current.Movie.SubPath
}
func (c *current) SetMovie(movie model.CurrentMovie, play bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
defer db.SetRoomCurrent(c.roomID, &c.current)
c.current.Movie = movie c.current.Movie = movie
c.current.SetSeek(0, 0) c.current.SetSeek(0, 0)
c.current.Status.IsPlaying = play c.current.Status.IsPlaying = play
} }
func (c *current) Status() Status { func (c *current) Status() model.Status {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
c.current.UpdateStatus() c.current.UpdateStatus()
return c.current.Status return c.current.Status
} }
func (c *current) SetStatus(playing bool, seek, rate, timeDiff float64) *Status { func (c *current) SetStatus(playing bool, seek, rate, timeDiff float64) *model.Status {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
defer db.SetRoomCurrent(c.roomID, &c.current)
s := c.current.SetStatus(playing, seek, rate, timeDiff) s := c.current.SetStatus(playing, seek, rate, timeDiff)
return &s return &s
} }
func (c *current) SetSeekRate(seek, rate, timeDiff float64) *Status { func (c *current) SetSeekRate(seek, rate, timeDiff float64) *model.Status {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
defer db.SetRoomCurrent(c.roomID, &c.current)
s := c.current.SetSeekRate(seek, rate, timeDiff) s := c.current.SetSeekRate(seek, rate, timeDiff)
return &s return &s
} }
func (c *Current) UpdateStatus() Status {
if c.Movie.IsLive {
c.Status.lastUpdate = time.Now()
return c.Status
}
if c.Status.IsPlaying {
c.Status.CurrentTime += time.Since(c.Status.lastUpdate).Seconds() * c.Status.PlaybackRate
}
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) setLiveStatus() Status {
c.Status.IsPlaying = true
c.Status.PlaybackRate = 1.0
c.Status.CurrentTime = 0
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
c.Status.IsPlaying = playing
c.Status.PlaybackRate = rate
if playing {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.PlaybackRate = rate
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeek(seek, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * c.Status.PlaybackRate)
} else {
c.Status.CurrentTime = seek
}
c.Status.lastUpdate = time.Now()
return c.Status
}

@ -26,16 +26,16 @@ import (
) )
type Movie struct { type Movie struct {
room *Room
*model.Movie *model.Movie
channel atomic.Pointer[rtmps.Channel] channel atomic.Pointer[rtmps.Channel]
alistCache atomic.Pointer[cache.AlistMovieCache] alistCache atomic.Pointer[cache.AlistMovieCache]
bilibiliCache atomic.Pointer[cache.BilibiliMovieCache] bilibiliCache atomic.Pointer[cache.BilibiliMovieCache]
embyCache atomic.Pointer[cache.EmbyMovieCache] embyCache atomic.Pointer[cache.EmbyMovieCache]
subPath string
} }
func (m *Movie) SubPath() string { func (m *Movie) SubPath() string {
return m.subPath return m.room.CurrentSubPath()
} }
func (m *Movie) ExpireID(ctx context.Context) (uint64, error) { func (m *Movie) ExpireID(ctx context.Context) (uint64, error) {
@ -99,7 +99,7 @@ func (m *Movie) ClearCache() error {
func (m *Movie) AlistCache() *cache.AlistMovieCache { func (m *Movie) AlistCache() *cache.AlistMovieCache {
c := m.alistCache.Load() c := m.alistCache.Load()
if c == nil { if c == nil {
c = cache.NewAlistMovieCache(m.Movie, m.subPath) c = cache.NewAlistMovieCache(m.Movie, m.SubPath())
if !m.alistCache.CompareAndSwap(nil, c) { if !m.alistCache.CompareAndSwap(nil, c) {
return m.AlistCache() return m.AlistCache()
} }
@ -121,7 +121,7 @@ func (m *Movie) BilibiliCache() *cache.BilibiliMovieCache {
func (m *Movie) EmbyCache() *cache.EmbyMovieCache { func (m *Movie) EmbyCache() *cache.EmbyMovieCache {
c := m.embyCache.Load() c := m.embyCache.Load()
if c == nil { if c == nil {
c = cache.NewEmbyMovieCache(m.Movie, m.subPath) c = cache.NewEmbyMovieCache(m.Movie, m.SubPath())
if !m.embyCache.CompareAndSwap(nil, c) { if !m.embyCache.CompareAndSwap(nil, c) {
return m.EmbyCache() return m.EmbyCache()
} }

@ -14,12 +14,14 @@ import (
type movies struct { type movies struct {
roomID string roomID string
room *Room
cache rwmap.RWMap[string, *Movie] cache rwmap.RWMap[string, *Movie]
} }
func (m *movies) AddMovie(mo *model.Movie) error { func (m *movies) AddMovie(mo *model.Movie) error {
mo.Position = uint(time.Now().UnixMilli()) mo.Position = uint(time.Now().UnixMilli())
movie := &Movie{ movie := &Movie{
room: m.room,
Movie: mo, Movie: mo,
} }
@ -45,6 +47,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error {
for _, mo := range mos { for _, mo := range mos {
mo.Position = uint(time.Now().UnixMilli()) mo.Position = uint(time.Now().UnixMilli())
movie := &Movie{ movie := &Movie{
room: m.room,
Movie: mo, Movie: mo,
} }
@ -184,7 +187,10 @@ func (m *movies) GetMovieByID(id string) (*Movie, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
mm, _ = m.cache.LoadOrStore(mv.ID, &Movie{Movie: mv}) mm, _ = m.cache.LoadOrStore(mv.ID, &Movie{
room: m.room,
Movie: mv,
})
return mm, nil return mm, nil
} }

@ -416,12 +416,12 @@ func (r *Room) GetMovieByID(id string) (*Movie, error) {
return r.movies.GetMovieByID(id) return r.movies.GetMovieByID(id)
} }
func (r *Room) Current() *Current { func (r *Room) Current() *model.Current {
c := r.current.Current() c := r.current.Current()
return &c return &c
} }
func (r *Room) CurrentMovie() CurrentMovie { func (r *Room) CurrentMovie() model.CurrentMovie {
return r.current.current.Movie return r.current.current.Movie
} }
@ -460,7 +460,7 @@ func (r *Room) SetCurrentMovie(movieID string, subPath string, play bool) error
} }
} }
if movieID == "" { if movieID == "" {
r.current.SetMovie(CurrentMovie{}, false) r.current.SetMovie(model.CurrentMovie{}, false)
return nil return nil
} }
m, err := r.GetMovieByID(movieID) m, err := r.GetMovieByID(movieID)
@ -470,14 +470,22 @@ func (r *Room) SetCurrentMovie(movieID string, subPath string, play bool) error
if m.IsFolder && !m.IsDynamicFolder() { if m.IsFolder && !m.IsDynamicFolder() {
return errors.New("cannot set static folder as current movie") return errors.New("cannot set static folder as current movie")
} }
m.subPath = subPath r.current.SetMovie(model.CurrentMovie{
r.current.SetMovie(CurrentMovie{ ID: m.ID,
ID: m.ID, IsLive: m.Live,
IsLive: m.Live, SubPath: subPath,
}, play) }, play)
return m.ClearCache() return m.ClearCache()
} }
func (r *Room) CurrentSubPath() string {
fmt.Println("CurrentSubPath", r.current)
fmt.Println("CurrentSubPath", r.current)
fmt.Println("CurrentSubPath", r.current)
fmt.Println("CurrentSubPath", r.current)
return r.current.SubPath()
}
func (r *Room) SwapMoviePositions(id1, id2 string) error { func (r *Room) SwapMoviePositions(id1, id2 string) error {
return r.movies.SwapMoviePositions(id1, id2) return r.movies.SwapMoviePositions(id1, id2)
} }
@ -512,11 +520,11 @@ func (r *Room) UserOnlineCount(userID string) int {
return r.lazyInitHub().OnlineCount(userID) return r.lazyInitHub().OnlineCount(userID)
} }
func (r *Room) SetCurrentStatus(playing bool, seek float64, rate float64, timeDiff float64) *Status { func (r *Room) SetCurrentStatus(playing bool, seek float64, rate float64, timeDiff float64) *model.Status {
return r.current.SetStatus(playing, seek, rate, timeDiff) return r.current.SetStatus(playing, seek, rate, timeDiff)
} }
func (r *Room) SetCurrentSeekRate(seek float64, rate float64, timeDiff float64) *Status { func (r *Room) SetCurrentSeekRate(seek float64, rate float64, timeDiff float64) *model.Status {
return r.current.SetSeekRate(seek, rate, timeDiff) return r.current.SetSeekRate(seek, rate, timeDiff)
} }

@ -54,11 +54,14 @@ func LoadOrInitRoom(room *model.Room) (*RoomEntry, error) {
return nil, err return nil, err
} }
i, _ := roomCache.LoadOrStore(room.ID, &Room{ r := &Room{
Room: *room, Room: *room,
current: newCurrent(), current: newCurrent(room.ID, room.Current),
movies: &movies{roomID: room.ID}, movies: &movies{roomID: room.ID},
}, time.Duration(settings.RoomTTL.Get())*time.Hour) }
r.movies.room = r
i, _ := roomCache.LoadOrStore(room.ID, r, time.Duration(settings.RoomTTL.Get())*time.Hour)
return i, nil return i, nil
} }

@ -509,7 +509,7 @@ func (u *User) GetRoomMoviesWithPage(room *Room, keyword string, page, pageSize
return room.GetMoviesWithPage(keyword, page, pageSize, parentID) return room.GetMoviesWithPage(keyword, page, pageSize, parentID)
} }
func (u *User) SetRoomCurrentStatus(room *Room, playing bool, seek, rate, timeDiff float64) (*Status, error) { func (u *User) SetRoomCurrentStatus(room *Room, playing bool, seek, rate, timeDiff float64) (*model.Status, error) {
if !u.HasRoomPermission(room, model.PermissionSetCurrentStatus) { if !u.HasRoomPermission(room, model.PermissionSetCurrentStatus) {
return nil, model.ErrNoPermission return nil, model.ErrNoPermission
} }

@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/model"
dbModel "github.com/synctv-org/synctv/internal/model" dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
pb "github.com/synctv-org/synctv/proto/message" pb "github.com/synctv-org/synctv/proto/message"
@ -449,7 +450,7 @@ func handleCheckStatusMessage(cli *op.Client, msg *pb.Message, timeDiff float64)
return nil return nil
} }
func needsSync(clientStatus *pb.Status, serverStatus op.Status, timeDiff float64) bool { func needsSync(clientStatus *pb.Status, serverStatus model.Status, timeDiff float64) bool {
if clientStatus.IsPlaying != serverStatus.IsPlaying || if clientStatus.IsPlaying != serverStatus.IsPlaying ||
clientStatus.PlaybackRate != serverStatus.PlaybackRate || clientStatus.PlaybackRate != serverStatus.PlaybackRate ||
serverStatus.CurrentTime+maxInterval < clientStatus.CurrentTime+timeDiff || serverStatus.CurrentTime+maxInterval < clientStatus.CurrentTime+timeDiff ||
@ -468,7 +469,7 @@ func sendErrorMessage(c *op.Client, errorMsg string) error {
}) })
} }
func sendSyncStatus(cli *op.Client, status *op.Status) error { func sendSyncStatus(cli *op.Client, status *model.Status) error {
return cli.Send(&pb.Message{ return cli.Send(&pb.Message{
Type: pb.MessageType_CHECK_STATUS, Type: pb.MessageType_CHECK_STATUS,
Payload: &pb.Message_PlaybackStatus{ Payload: &pb.Message_PlaybackStatus{

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
json "github.com/json-iterator/go" json "github.com/json-iterator/go"
"github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/utils" "github.com/synctv-org/synctv/utils"
) )
@ -206,9 +205,9 @@ type Movie struct {
} }
type CurrentMovieResp struct { type CurrentMovieResp struct {
Movie *Movie `json:"movie"` Movie *Movie `json:"movie"`
Status op.Status `json:"status"` Status model.Status `json:"status"`
ExpireID uint64 `json:"expireId"` ExpireID uint64 `json:"expireId"`
} }
type ClearMoviesReq struct { type ClearMoviesReq struct {

@ -1 +1 @@
Subproject commit 96adf55ec2b4eb84ad890c47db25bccb548ea725 Subproject commit 6f2411e004a7957c4be8e2f7d5129b42362fc896
Loading…
Cancel
Save