diff --git a/internal/db/room.go b/internal/db/room.go index 1b544e2a..61ebf3b8 100644 --- a/internal/db/room.go +++ b/internal/db/room.go @@ -184,3 +184,14 @@ func SetRoomStatusByCreator(userID string, status model.RoomStatus) error { result := db.Model(&model.Room{}).Where("creator_id = ?", userID).Update("status", status) 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) +} diff --git a/internal/db/update.go b/internal/db/update.go index 3e6a62b4..80b383c4 100644 --- a/internal/db/update.go +++ b/internal/db/update.go @@ -15,7 +15,7 @@ type dbVersion struct { NextVersion string } -const CurrentVersion = "0.0.12" +const CurrentVersion = "0.0.13" var models = []any{ new(model.Setting), @@ -82,6 +82,9 @@ var dbVersions = map[string]dbVersion{ NextVersion: "0.0.12", }, "0.0.12": { + NextVersion: "0.0.13", + }, + "0.0.13": { NextVersion: "", }, } diff --git a/internal/model/current.go b/internal/model/current.go new file mode 100644 index 00000000..b3bd5325 --- /dev/null +++ b/internal/model/current.go @@ -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 +} diff --git a/internal/model/room.go b/internal/model/room.go index b0f8c399..1661018a 100644 --- a/internal/model/room.go +++ b/internal/model/room.go @@ -41,6 +41,7 @@ type Room struct { RoomMembers []*RoomMember `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"` + Current *Current `gorm:"serializer:fastjson"` } func (r *Room) BeforeCreate(tx *gorm.DB) error { diff --git a/internal/op/current.go b/internal/op/current.go index da58d865..c0552606 100644 --- a/internal/op/current.go +++ b/internal/op/current.go @@ -2,144 +2,76 @@ package op import ( "sync" - "time" + + "github.com/synctv-org/synctv/internal/db" + "github.com/synctv-org/synctv/internal/model" ) type current struct { - current Current + roomID string + current model.Current lock sync.RWMutex } -type Current struct { - Movie CurrentMovie - Status Status -} - -type CurrentMovie struct { - ID string - IsLive bool -} - -func newCurrent() *current { - return ¤t{ - current: Current{ - Status: newStatus(), - }, +func newCurrent(roomID string, c *model.Current) *current { + if c == nil { + return ¤t{ + roomID: roomID, + current: model.Current{ + Status: model.NewStatus(), + }, + } } -} - -type Status struct { - 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(), + return ¤t{ + roomID: roomID, + current: *c, } } -func (c *current) Current() Current { +func (c *current) Current() model.Current { c.lock.RLock() defer c.lock.RUnlock() c.current.UpdateStatus() 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() defer c.lock.Unlock() + defer db.SetRoomCurrent(c.roomID, &c.current) c.current.Movie = movie c.current.SetSeek(0, 0) c.current.Status.IsPlaying = play } -func (c *current) Status() Status { +func (c *current) Status() model.Status { c.lock.RLock() defer c.lock.RUnlock() c.current.UpdateStatus() 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() defer c.lock.Unlock() + defer db.SetRoomCurrent(c.roomID, &c.current) s := c.current.SetStatus(playing, seek, rate, timeDiff) return &s } -func (c *current) SetSeekRate(seek, rate, timeDiff float64) *Status { +func (c *current) SetSeekRate(seek, rate, timeDiff float64) *model.Status { c.lock.Lock() defer c.lock.Unlock() + defer db.SetRoomCurrent(c.roomID, &c.current) s := c.current.SetSeekRate(seek, rate, timeDiff) 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 -} diff --git a/internal/op/movie.go b/internal/op/movie.go index a0c2ff7a..d8cfcbbc 100644 --- a/internal/op/movie.go +++ b/internal/op/movie.go @@ -26,16 +26,16 @@ import ( ) type Movie struct { + room *Room *model.Movie channel atomic.Pointer[rtmps.Channel] alistCache atomic.Pointer[cache.AlistMovieCache] bilibiliCache atomic.Pointer[cache.BilibiliMovieCache] embyCache atomic.Pointer[cache.EmbyMovieCache] - subPath string } func (m *Movie) SubPath() string { - return m.subPath + return m.room.CurrentSubPath() } func (m *Movie) ExpireID(ctx context.Context) (uint64, error) { @@ -99,7 +99,7 @@ func (m *Movie) ClearCache() error { func (m *Movie) AlistCache() *cache.AlistMovieCache { c := m.alistCache.Load() if c == nil { - c = cache.NewAlistMovieCache(m.Movie, m.subPath) + c = cache.NewAlistMovieCache(m.Movie, m.SubPath()) if !m.alistCache.CompareAndSwap(nil, c) { return m.AlistCache() } @@ -121,7 +121,7 @@ func (m *Movie) BilibiliCache() *cache.BilibiliMovieCache { func (m *Movie) EmbyCache() *cache.EmbyMovieCache { c := m.embyCache.Load() if c == nil { - c = cache.NewEmbyMovieCache(m.Movie, m.subPath) + c = cache.NewEmbyMovieCache(m.Movie, m.SubPath()) if !m.embyCache.CompareAndSwap(nil, c) { return m.EmbyCache() } diff --git a/internal/op/movies.go b/internal/op/movies.go index b2eaf880..5498ee33 100644 --- a/internal/op/movies.go +++ b/internal/op/movies.go @@ -14,12 +14,14 @@ import ( type movies struct { roomID string + room *Room cache rwmap.RWMap[string, *Movie] } func (m *movies) AddMovie(mo *model.Movie) error { mo.Position = uint(time.Now().UnixMilli()) movie := &Movie{ + room: m.room, Movie: mo, } @@ -45,6 +47,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error { for _, mo := range mos { mo.Position = uint(time.Now().UnixMilli()) movie := &Movie{ + room: m.room, Movie: mo, } @@ -184,7 +187,10 @@ func (m *movies) GetMovieByID(id string) (*Movie, error) { if err != nil { 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 } diff --git a/internal/op/room.go b/internal/op/room.go index ecf4f777..084b6b50 100644 --- a/internal/op/room.go +++ b/internal/op/room.go @@ -416,12 +416,12 @@ func (r *Room) GetMovieByID(id string) (*Movie, error) { return r.movies.GetMovieByID(id) } -func (r *Room) Current() *Current { +func (r *Room) Current() *model.Current { c := r.current.Current() return &c } -func (r *Room) CurrentMovie() CurrentMovie { +func (r *Room) CurrentMovie() model.CurrentMovie { return r.current.current.Movie } @@ -460,7 +460,7 @@ func (r *Room) SetCurrentMovie(movieID string, subPath string, play bool) error } } if movieID == "" { - r.current.SetMovie(CurrentMovie{}, false) + r.current.SetMovie(model.CurrentMovie{}, false) return nil } 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() { return errors.New("cannot set static folder as current movie") } - m.subPath = subPath - r.current.SetMovie(CurrentMovie{ - ID: m.ID, - IsLive: m.Live, + r.current.SetMovie(model.CurrentMovie{ + ID: m.ID, + IsLive: m.Live, + SubPath: subPath, }, play) 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 { return r.movies.SwapMoviePositions(id1, id2) } @@ -512,11 +520,11 @@ func (r *Room) UserOnlineCount(userID string) int { 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) } -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) } diff --git a/internal/op/rooms.go b/internal/op/rooms.go index 677dada6..26a62eb5 100644 --- a/internal/op/rooms.go +++ b/internal/op/rooms.go @@ -54,11 +54,14 @@ func LoadOrInitRoom(room *model.Room) (*RoomEntry, error) { return nil, err } - i, _ := roomCache.LoadOrStore(room.ID, &Room{ + r := &Room{ Room: *room, - current: newCurrent(), + current: newCurrent(room.ID, room.Current), 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 } diff --git a/internal/op/user.go b/internal/op/user.go index 4436767b..9de17a17 100644 --- a/internal/op/user.go +++ b/internal/op/user.go @@ -509,7 +509,7 @@ func (u *User) GetRoomMoviesWithPage(room *Room, keyword string, page, pageSize 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) { return nil, model.ErrNoPermission } diff --git a/server/handlers/websocket.go b/server/handlers/websocket.go index 12b579d2..f111b393 100644 --- a/server/handlers/websocket.go +++ b/server/handlers/websocket.go @@ -11,6 +11,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" log "github.com/sirupsen/logrus" + "github.com/synctv-org/synctv/internal/model" dbModel "github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/op" pb "github.com/synctv-org/synctv/proto/message" @@ -449,7 +450,7 @@ func handleCheckStatusMessage(cli *op.Client, msg *pb.Message, timeDiff float64) 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 || clientStatus.PlaybackRate != serverStatus.PlaybackRate || 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{ Type: pb.MessageType_CHECK_STATUS, Payload: &pb.Message_PlaybackStatus{ diff --git a/server/model/movie.go b/server/model/movie.go index cd1fac73..521ed8d9 100644 --- a/server/model/movie.go +++ b/server/model/movie.go @@ -8,7 +8,6 @@ import ( "github.com/gin-gonic/gin" json "github.com/json-iterator/go" "github.com/synctv-org/synctv/internal/model" - "github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/utils" ) @@ -206,9 +205,9 @@ type Movie struct { } type CurrentMovieResp struct { - Movie *Movie `json:"movie"` - Status op.Status `json:"status"` - ExpireID uint64 `json:"expireId"` + Movie *Movie `json:"movie"` + Status model.Status `json:"status"` + ExpireID uint64 `json:"expireId"` } type ClearMoviesReq struct { diff --git a/synctv-web b/synctv-web index 96adf55e..6f2411e0 160000 --- a/synctv-web +++ b/synctv-web @@ -1 +1 @@ -Subproject commit 96adf55ec2b4eb84ad890c47db25bccb548ea725 +Subproject commit 6f2411e004a7957c4be8e2f7d5129b42362fc896